C++实现简单线程池

线程池

尽管C++11加入线程库,但C++对多线程的支持还是比较初级,稍微高级的用法还是需要自己实现。线程池是提前创建并维护多个线程,等待管理者分配任务的机制,避免短时间线程创建和销毁的代价,一般是IO密集型的场景使用。主要包括线程管理器、任务线程、消息队列

  • 线程管理器:主要功能是创建和启动线程、调配任务、管理线程等。主要有三个方法:

    • start:创建一定数量线程池
    • stop:终止所有线程并回收资源
    • addTask:添加任务到消息队列
  • 任务线程:等待分配任务的线程,一般使用条件变量实现等待和通知机制

  • 任务队列:存放任务的缓冲机制,队列有调度功能,使用优先队列实现,但需要锁限制并发

线程池工作的几种情况

按任务队列和线程池大小可分成四种情况:

  1. 没有任务,线程池中任务队列为空,啥也不做
  2. 添加小于等于线程池数量的任务,主线程添加任务后通知唤醒线程池中的线程开始取任务。此时任务缓冲队列还是空
  3. 添加大于线程池数量的任务,继续添加发现线程池用完,于是存入缓冲队列,工作线程空闲后主动从任务队列取任务执行
  4. 添加大于线程池数量的任务,且任务队列已满,当线程中线程用完,且任务缓冲队列已满,进入等待状态,等待任务缓冲队列通知

线程池的实现

声明

使用C++11中的bind/function定义和调用任务处理函数

任务处理函数的声明、优先级、带优先级的任务:

typedef std::function<void()> Task_type;//任务类型
enum taskPriorityE {LOW,MIDDLE,HIGH};//优先级
typedef std::pair<taskPriorityE,Task_type> TaskPair;//任务优先级和任务类型组合的任务

禁用拷贝构造和赋值运算符:

ThreadPool(const ThreadPool&);
const ThreadPool& operator=(const ThreadPool&);

声明线程池大小、任务队列、互斥锁和条件变量、线程池是否开始

int m_threads_size;//线程池大小
std::vector<std::thread*> m_threads;//线程池
std::priority_queue<TaskPair,std::vector<TaskPair>,TaskPriorityCmp>  m_tasks;//任务队列
std::mutex m_mutex;//STL队列不是线程安全的,因此需要结合互斥锁
std::condition_variable m_cond;//条件变量
bool m_started;//是否开始

另外值得注意的是为了安全性和便捷性,只暴露stop和addTask两个接口给用户,其他start和threadLoop、take等接口都被声明为私有函数,

实现

构造函数通过传入的参数,初始化线程池大小、锁、条件变量、是否开始,之后开始运行

ThreadPool::ThreadPool(int threads_size)
    :m_threads_size(threads_size),m_mutex(),m_cond(),m_started(false)
{
    start();
}

start中创建线程,并将线程和任务处理函数进行绑定

void ThreadPool::start()
{
    assert(m_threads.empty());
    assert(!m_started);
    m_started = true;
    m_threads.reserve(m_threads_size);
    for(int i=0;i<m_threads_size;++i)
    {
        m_threads.push_back(new std::thread(std::bind(&ThreadPool::threadLoop,this)));
    }
}

stop中通知所有线程,并将所有线程分离,最后将线程池清空

void ThreadPool::stop()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    m_started = false;
    m_cond.notify_all();

    for(auto it= m_threads.begin();it!=m_threads.end();++it)
    {
        (*it)->join();
        delete *it;
    }
    m_threads.clear();
}

threadLoop中循环从队列中拿任务并执行

void ThreadPool::threadLoop()
{
    while(m_started)
    {
        Task_type task = take();
        if(task)
            task();
    }
}

addTask是添加任务到任务队列,并通知线程。为方便使用重载addTask函数

void ThreadPool::addTask(const Task_type& task)
{
    std::unique_lock<std::mutex> lock(m_mutex);
    TaskPair taskPair(MIDDLE,task);
    m_tasks.emplace(taskPair);
    m_cond.notify_one();
}

void ThreadPool::addTask(const TaskPair& taskPair)
{

    std::unique_lock<std::mutex> lock(m_mutex);
    m_tasks.emplace(taskPair);
    m_cond.notify_one();
}

take从队列中拿任务,若队列为空且已开始,则等待,对应上面的情况1,若不空则从队列拿任务并返回

ThreadPool::Task_type ThreadPool::take()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    while(m_tasks.empty()&&m_started)
    {
        m_cond.wait(lock);
    }

    Task_type task;

    int size = m_tasks.size();
    if(!m_tasks.empty()&&m_started)
    {
        task = m_tasks.top().second;
        m_tasks.pop();
        assert(size -1 == m_tasks.size());
    }
    return task;
}

最后提醒下,由于STL的队列不是线程安全,因此对队列的添加addTask、删除take都需要锁,当然后续会改进成使用boost库中线程安全的队列,这样就能大大提高并发性。

总结

使用C++11中的thread、mutex、condition_variable、priority_queue、vector实现简单的线程池。后续考虑使用无锁队列优化。完整代码见这里

  • 3
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
线程池是一种用于管理和复用线程的机制,它可以提高多线程程序的性能和可扩展性。线程池中包含一组预先创建的线程,这些线程可以处理任务队列中的任务,而不需要每次都创建和销毁线程。 线程池的原理如下: 1. 创建线程池:首先,创建一个包含固定数量的线程的线程池。这些线程处于空闲状态,等待任务的到来。 2. 提交任务:当有任务需要执行时,将任务提交到线程池。 3. 任务队列:线程池中有一个任务队列,用于存储待执行的任务。当有任务提交时,将任务放入队列中。 4. 任务调度:线程池中的空闲线程会从任务队列中获取任务进行执行。线程会不断地从任务队列中获取任务,直到队列为空。 5. 执行任务:线程从任务队列中获取任务后,执行该任务。 6. 线程复用:当一个线程完成了一个任务后,它会再次进入空闲状态,等待下一个任务的到来。这样可以避免频繁地创建和销毁线程,提高了性能和效率。 下面是一个简单的C语言实现线程池的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_POOL_SIZE 5 typedef struct { void (*task)(void *); void *arg; } Task; typedef struct { pthread_t thread; int is_working; } Worker; Task task_queue[THREAD_POOL_SIZE]; Worker workers[THREAD_POOL_SIZE]; pthread_mutex_t mutex; pthread_cond_t cond; void *worker_thread(void *arg) { while (1) { pthread_mutex_lock(&mutex); // 等待任务到来 while (task_queue[*(int *)arg].task == NULL) { pthread_cond_wait(&cond, &mutex); } // 执行任务 Task task = task_queue[*(int *)arg]; task.task(task.arg); // 清空任务 task_queue[*(int *)arg].task = NULL; pthread_mutex_unlock(&mutex); } } void thread_pool_init() { int i; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); for (i = 0; i < THREAD_POOL_SIZE; i++) { workers[i].is_working = 0; task_queue[i].task = NULL; pthread_create(&workers[i].thread, NULL, worker_thread, &i); } } void thread_pool_submit(void (*task)(void *), void *arg) { pthread_mutex_lock(&mutex); // 查找空闲线程 int i; for (i = 0; i < THREAD_POOL_SIZE; i++) { if (task_queue[i].task == NULL) { task_queue[i].task = task; task_queue[i].arg = arg; pthread_cond_signal(&cond); break; } } pthread_mutex_unlock(&mutex); } void my_task(void *arg) { int id = *(int *)arg; printf("Task executed by thread %d\n", id); } int main() { int i; thread_pool_init(); // 提交任务 for (i = 0; i < 10; i++) { int *arg = malloc(sizeof(int)); *arg = i; thread_pool_submit(my_task, arg); } // 等待任务完成 sleep(1); return 0; } ``` 这是一个简单线程池实现。在主函数中,我们初始化了线程池,然后提交了10个任务。每个任务都打印出当前执行任务的线程ID。最后,我们等待1秒钟,以确保所有任务都被执行完毕。 请注意,这只是一个简单示例,实际上线程池实现可能需要更多的细节和线程安全机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值