简单的线程池实现&智能指针的简单应用

简单的线程池实现&智能指针的简单应用

简单的线程池实现

为了减少线程切换等操作所带来的开销,这里介绍一个简单的线程池的实现过程。
首先线程池主要由两个部分组成,分别是存放线程的容器和存放任务的队列。设计思路如下图流程图所示:线程池主要有两个执行函数。其中一个函数负责运行任务,也就是每个线程的运行实体,另一个函数负责放置任务到线程对应的队列。
在这里插入图片描述

线程池的定义如下:这里面thread_pool_vec是线程的存放容器;task_queue存放任务队列,每个线程对应自己的任务队列,使用双向队列的存放任务,是因为我认为双向队列为任务的存取提供更灵活的选择。锁和条件资源由于不支持拷贝,我把它们声明为指针变量,当然,我认为指针容器类型存放锁是更好的选择,避免忘记delete或重复delete等等问题。

#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H
 
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <deque>
#include <functional>
#include <condition_variable>
 
typedef std::function<void()> Task;             //任务的模板
 
class ThreadPool
{
 
private:
 
    std::vector<std::thread>  thread_pool_vec;      //线程池的容器
 
    std::vector<std::deque<Task>>   task_queue;     //任务队列
 
    std::vector<bool>   is_running;                 //运行标志
 
    std::mutex* mutex_queue;                        //读写锁队列
 
    std::condition_variable* not_empty;             //非空条件变量
 
    std::condition_variable* not_full;              //非满变量
 
    int num_of_thread;                              //线程的数量
 
public:
 
    ThreadPool(const int& thread_num)
    {
        //根据thread_num初始化线程池的大小
        if(thread_num > 0)
        {
            thread_pool_vec.reserve(thread_num);
            task_queue.reserve(thread_num);
            start(thread_num);
        }
    }
 
    ~ThreadPool()
    {
        delete []mutex_queue;    //析构时记得把原始指针的变量都释放了
        delete []not_empty;
        delete []not_full;
        mutex_queue = NULL;
        not_empty = NULL;
        not_full = NULL;
    }
 
    //放任务的函数
    bool addTask(Task task, const int& index);
 
private:
 
    //运行任务的函数
    void executeTask(int index);
 
    //启动线程的函数
    bool start(const int& thread_num);
 
    //终止线程的函数
    bool stop(const int& index);
 
    bool isFull(const int& index);
 
};
 
 
#endif

出于安全考虑,我还是使用了指针容器。需要注意,使用指针容器下标获取的元素,是解引用后的元素,不需要手动解引用。

#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H
 
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <deque>
#include <functional>
#include <condition_variable>
#include <boost/ptr_container/ptr_vector.hpp>
 
typedef std::function<void()> Task;             //任务的模板
 
class ThreadPool
{
 
private:
 
    std::vector<std::thread>  thread_pool_vec;      //线程池的容器
 
    std::vector<std::deque<Task>>   task_queue;     //任务队列
 
    boost::ptr_vector<std::mutex>   mutex_queue;    //读写锁队列
 
    std::vector<bool>   is_running;                 //运行标志
 
    boost::ptr_vector<std::condition_variable>  not_empty;  //非空条件变量
 
    boost::ptr_vector<std::condition_variable>  not_full;   //非满变量
 
    int num_of_thread;                              //线程的数量
 
public:
 
    ThreadPool(const int& thread_num)
    {
        //根据thread_num初始化线程池的大小
        if(thread_num > 0)
        {
            thread_pool_vec.reserve(thread_num);
            task_queue.reserve(thread_num);
            mutex_queue.reserve(thread_num);
            not_empty.reserve(thread_num);
            not_full.reserve(thread_num);
            start(thread_num);
        }
    }
 
    ~ThreadPool()
    {
    }
 
    //放任务的函数
    bool addTask(Task task, const int& index);
 
private:
 
    //运行任务的函数
    void executeTask(int index);
 
    //启动线程的函数
    bool start(const int& thread_num);
 
    //终止线程的函数
    bool stop(const int& index);
 
    bool isFull(const int& index);
 
};
 
 
#endif

由上面代码可见,任务的实体Task,是一个无参数的函数模板,所以在调用是需要注意绑定参数。

下面是整个线程池的具体实现:

#include "./thread_pool.h"
 
bool ThreadPool::isFull(const int& index)
{
    int que_size = task_queue[index].size();
    int num_size = 10;    //此处设置了一个任务队列的最大值,需要实验验证是否合理
    return que_size > num_size ? true : false; 
 
}
 
 
bool ThreadPool::addTask(Task task, const int& index)
{
 
    std::unique_lock<std::mutex> mutex_lock((mutex_queue[index]));
    //判断是否已经满队了
    while(isFull(index))
    {
        std::cout<<"index: "<<index<<"task_queue is full"<<std::endl;
        //调用条件变量
        not_full[index].wait(mutex_lock);
    }
 
    task_queue[index].push_back(task);
 
    //只有一个线程使用,所以使用notify_one()
    not_empty[index].notify_one();
 
    return true;
 
}
 
void ThreadPool::executeTask(int index)
{
    while(is_running[index])
    {
        std::unique_lock<std::mutex> mutex_lock((mutex_queue[index]));
 
        //判断队列是否为空
        while(task_queue[index].empty())
        {
            std::cout<<"index: "<<index<<"task_queue is empty"<<std::endl;
            //调用条件变量
            not_empty[index].wait(mutex_lock);
        }
        //取任务队列首个任务
        if(task_queue[index].front() != NULL)
        {
            Task task = task_queue[index].front();
            task_queue[index].pop_front();
            //执行任务
            task();
        }
        not_full[index].notify_one();
    }
 
}
 
bool ThreadPool::start(const int& thread_num)
{
    num_of_thread = thread_num;
    //循环开启线程
    for(int i = 0; i < thread_num; i++)
    {
 
        thread_pool_vec.push_back(std::thread(&ThreadPool::executeTask, this, i));
 
        thread_pool_vec[i].detach();    //分离线程
 
        mutex_queue.push_back(new std::mutex());    
 
        is_running.push_back(true);
 
        not_empty.push_back(new std::condition_variable());
 
        not_full.push_back(new std::condition_variable());
 
        std::deque<Task> task_deque;
 
        task_queue.push_back(task_deque);
 
    }
    return true;
}
 
bool ThreadPool::stop(const int& index)
{
    //检查线程是否还在运行
    if(is_running[index] == false)return false;
 
    //停止线程
    is_running[index] = false;
 
    return true;
}

其中我是用的是一对条件变量,一个为非满条件,另一个未非空条件。将条件变量设置为一个,逻辑上找不到问题,但效率是否会被影响,暂时未证实。

#include"../thread_pool/thread_pool.h"
void func(int index)
{
	std::cout<<"Task: "<<index<<std::endl;
}
 
int main() 
{
	int num = 0;
	std::cin>>num;
	ThreadPool *threadpool = new ThreadPool(num);
	while(true)
	{
		srand((unsigned int)time(NULL));
		int index = rand()%num;
		Task task = std::bind(func, index);
		threadpool->addTask(task, index);
	}
 
	delete threadpool;
	threadpool = NULL;
 
	return 0;
}

按照以上代码做的测试结果:明显看出由于任务放置速度与线程运行任务的速度不匹配,造成很多线程对应的任务队列为空。(工人多,订单数,恰好反映了现在工厂的情况)
在这里插入图片描述

智能指针的应用

C++动态分配内存到原始指针的烦恼:
1、忘了释放:new和delete没有配对出现,造成内存泄漏;
2、忘记已经释放过了,重复释放;
3、忘记对象的其他引用的生命周期还未结束,就已经提前释放了对象;
所以,编程最重要的就是记性。
既然万物皆对象,C++中的指针也应该像栈上的对象一样,可以在离开作用域后自动释放。为了实现这一点,引入了智能指针的概念,C++11及之后的版本导入就可以使用智能指针了。
首先需要介绍的是unique_ptr,从字面意思理解,该指针是唯一的不可拷贝的。

std::unique_ptr<ListNode<int>> unique_node(new ListNode<int>());
//转移所有权
	std::unique_ptr<ListNode<int>> unique_node_copy = std::move(unique_node);
	unique_node_copy->value = 100;
	//测试是否拥有对象的所有权
	std::cout<<"unique_node_copy->value: "<<(*unique_node_copy).value<<std::endl;

测试的结果:
在这里插入图片描述
shared_ptr共享指针,是有共享的引用计数器。

//共享型指针
	//std::shared_ptr<ListNode<int>> list_node1(new ListNode<int>()); 
	std::shared_ptr<ListNode<int>> list_node1
	= std::make_shared<ListNode<int>>();

	std::shared_ptr<ListNode<int>> list_node2 
	= std::make_shared<ListNode<int>>();

共享指针有引用循环的问题。首先介绍引用循环的概念,以下是我定义的一个双向链表的节点类型。

template<typename NodeType>
class ListNode
{

public:
    std::share_ptr<ListNode<NodeType>>    pre_node;             //向前的指针
    std::share_ptr<ListNode<NodeType>>    next_node;          //向后的指针
    NodeType value;                                 //节点变量

public:
    ListNode()
    {
        
    }
    ~ListNode()
    {
        std::cout<<"Free list node"<<std::endl;
    }

    inline int getPreUseCount()
    {
        return pre_node.use_count();
    }

    inline int getNextUseCount()
    {
        return next_node.use_count();
    }
};

在上面的代码里,我使用到共享指针来作为向前和向后的指针。如:node1的next指针指向node2,node2的pre指向node1。但是当node1被销毁后,由于node2的pre仍然存在,node1的引用计数仍然为1,无法释放。同样node1的next指向的node2也将无法被释放。这个时候,需要配合使用weak_ptr,一个弱引用的智能指针,它将更好地诠释引用的作用。

template<typename NodeType>
class ListNode
{

public:
    std::weak_ptr<ListNode<NodeType>>    pre_node;   //向前的指针
    std::weak_ptr<ListNode<NodeType>>    next_node;  //向后的指针
    NodeType value;                                 //节点变量

public:
    ListNode()
    {
        
    }
    ~ListNode()
    {
        std::cout<<"Free list node"<<std::endl;
    }

    inline int getPreUseCount()
    {
        return pre_node.use_count();
    }

    inline int getNextUseCount()
    {
        return next_node.use_count();
    }
};

由于是弱引用,当我们输出next_node或者pre_node的时候,会发现它们的引用计数都是1,当node1被释放后,node2的pre指针的引用计数会变成1-1 = 0;通过下面的测试代码:

	//唯一型智能指针
	std::unique_ptr<ListNode<int>> unique_node(new ListNode<int>());
	//std::unique_ptr<ListNode<int>> unique_node_copy = unique_node;	//无法拷贝
	//转移所有权
	std::unique_ptr<ListNode<int>> unique_node_copy = std::move(unique_node);
	unique_node_copy->value = 100;
	//测试是否拥有对象的所有权
	std::cout<<"unique_node_copy->value: "<<(*unique_node_copy).value<<std::endl;

	//共享型指针
	//std::shared_ptr<ListNode<int>> list_node1(new ListNode<int>()); 
	std::shared_ptr<ListNode<int>> list_node1
	= std::make_shared<ListNode<int>>();

	std::shared_ptr<ListNode<int>> list_node2 
	= std::make_shared<ListNode<int>>();

	//shared_ptr可以直接拷贝到weak_ptr
	list_node1->next_node = list_node2;
	list_node2->pre_node = list_node1;
	list_node1->value = 1;
	list_node2->value = 2;

	//打印向后指针的引用计数
	std::cout<<"use_count of list_node1->next_node: "
	<<list_node1->getNextUseCount()<<std::endl;

	//打印值
	std::cout<<"list_node2 vaule : "
	<<list_node1->next_node.lock()->value<<"\n"
	<<"list_node1 vaule : "<< list_node2->pre_node.lock()->value<<std::endl;

测试结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值