走进C++11(三十一) 如何用60行实现C++11 thread pool

关注公众号获取更多信息:

 

开始之前先上一下代码链接:

 

https://github.com/maxcong001/threadPool

 

最近看了看我的计划,写道这里也算是到了一半,大部分都是讲的单一的C++11的用法,基本都是理论知识,就像我上大学的时候,老师一直讲理论知识,结局就是能去能不去的时候,我选择了后者。所以在这里穿插一下小的综合运用文章,让大家知道为什么要用C++11,C++11好在哪里,项目中如何运用C++11.

 

首先介绍一下背景。在我们的工作中,避免不了多线程之间的配合。在现在处理器动辄8核16核的背景下,如果我们的程序还停留在单线程的模型,那么我们就没法享受多处理器带来的性能提成。之前看过我司代码中的threadpool。写的那叫一个滴水不漏,每个小细节都有大量的代码去实现。不但非常冗长,而且以我的智商基本上读不懂。唯一的有点就是:真稳定。不过threadpool的模型已经很难融入现代C++了。

 

所以有必要通过C++11来重新实现一下threadpool,对比一下modern

C++和C98.

 

1. 为什么要有threadpool?

 

如果谈论threadpool,你会想到有什么功能呢?

 

传统的模型大概是这样的,把一个函数指针传给threadpool。然后thread们会在合适的时候调用这个函数。那么还有一个问题就是函数的返回值怎么传递回调用的线程。这个功能往往有很多种方法,我司的思路就是调用你的callback将函数返回值返回给你。当然不是返回给调用函数的线程。

 

以上的描述中反映的threadpool的两个最基本的需求:

 

  1.  可以把一个可执行的对象扔给threadpool去执行。

  2. 可以把执行的返回值带回。

 

其实这就是threadpool存在的合理性-- 把工作扔给它,我只要知道结果就行。当然任务扔给threadpool后,你就可以去干一些别的工作。

 

有人会说,扔给threadpool,无非是让别的线程去干活,干的总活并没有减少。相反,一些threadpool的开销反而让工作变的更慢。至于这个问题我想用redis来举例子。

 

众所周知,redis最新版本支持的多线程。redis的作者在解释为什么引入多线程的时候说过。在他们维护redis的时候,发现redis的瓶颈竟然出现在分配内存上(从socket上拷贝内存)。所以你会发现redis起了多线,只是为了加速内存拷贝,最终的逻辑还是在一个线程执行的。所以可以看出,可以把较慢的代码或者可以流水操作的代码让不同的线程执行。

 

2. 现代化threadpool提出了什么更高的要求?

 

之前我们分享过std::function。std::function 是C++11提供的可执行代码的包装器,它可以是一个普通函数,或者是函数指针,或者是lambda...,所以对于我们来说,threadpool也要支持std::function能支持的类型。

 

关于返回值,还有如何返回到calling thread,之前我们也分享过std::future.

 

如果大家忘记了这两个概念,可以回去找找之前的文章复习一下。

走进C++11(二十三) 函数对象包装器之std::function

走进C++11(二十七) 处理未来发生的事情 std::future

 

还有就是线程间的同步,之前我们分享过 std::condition_variable,如果忘记了,看看这个吧:

走进C++11(三十)标准化条件变量 -- condition_variable

 

还有就是thread的包装器,我们用了std::thread,同样,如果记不清了,看看这个:

走进C++11(二十四)一统江湖之线程 -- std::thread

 

 

至此我们凑齐了实现threadpool的几大件,下面我们看看如何来实现它

3. 原理:

3.1 对象定义

要实现一个threadpool。我们要有以下的信息:

 

  1. 我们要有个结构体,记住我们控制的thread。

  2. 我们要有个结构体,记住我们要做的事情。

  3. 我们要有个condition_variable来做线程间同步。

  4. 为了优雅的推出,我们要有个标志位,标志着我现在想推出了,大家都退下吧。

     

功能上要有:

  1. 构造函数

  2. 析构函数

  3. 最重要的 -- 添加任务的函数

 

实现起来如下:

 

class ThreadPool{  public:    ThreadPool(size_t);    template <class F, class... Args>    auto enqueue(F &&f, Args &&... args) -> std::future<typename std::result_of<F(Args...)>::type>;    ~ThreadPool();
  private:    // need to keep track of threads so we can join them    std::vector<std::thread> workers;    // the task queue    std::queue<std::function<void()>> tasks;    // synchronization    std::mutex queue_mutex;    std::condition_variable condition;    bool stop;};

 

 

3.2 初始化

 

这里构建了我们需要的thread。并把它放在一个vector里。

 

这个thread只干一件事,那就是等condition_variable的通知,如果有通知,那么从task queue里边拿出一个task,并执行该task。

 

当然还有一些判断是否退出的逻辑。

 

inline ThreadPool::ThreadPool(size_t threads)    : stop(false){    for (size_t i = 0; i < threads; ++i)        workers.emplace_back(            [this] {                for (;;)                {                    std::function<void()> task;                    {                        std::unique_lock<std::mutex> lock(this->queue_mutex);                        this->condition.wait(lock,                                             [this] { return this->stop || !this->tasks.empty(); });                        if (this->stop && this->tasks.empty())                            return;                        task = std::move(this->tasks.front());                        this->tasks.pop();                    }                    task();                }            });}

 

4. 添加任务API

 

说到函数,不可避免的就是函数的实现和函数的参数,为了实现支持不同的类型,我们选择了用模板来适配。

 

同时为了得到返回值,我们将返回值设置成了future。那么问题来了,这该如何实现?是不是想起了packaged_task? 如果忘了,回忆一下吧。

走进C++11(二十九) 将工作打包成任务,丢给执行者 -- std::packaged_task

 

packaged_task可以将可执行的工作打包,然后获取它的future。

 

至此我们就可以实现我们的功能了。思路就是来了一个可执行的工作,首先封装成packaged_task。然后把这个task放到task queue中。并且通知一个线程说queue里边有东西了,赶紧去干活。

 

在返回之前,得到它的future并返回。

 

实现如下:

 

template <class F, class... Args>auto ThreadPool::enqueue(F &&f, Args &&... args)    -> std::future<typename std::result_of<F(Args...)>::type>{    using return_type = typename std::result_of<F(Args...)>::type;    auto task = std::make_shared<std::packaged_task<return_type()>>(        std::bind(std::forward<F>(f), std::forward<Args>(args)...));    std::future<return_type> res = task->get_future();    {        std::unique_lock<std::mutex> lock(queue_mutex);        // don't allow enqueueing after stopping the pool        if (stop)            throw std::runtime_error("enqueue on stopped ThreadPool");        tasks.emplace([task]() { (*task)(); });    }    condition.notify_one();    return res;}

 

至此,所有功能都实现了,有了C++11,是不是一切都变得美好了起来,用了60行就实现了以前无数行才能实现的功能,而且简单易懂,支持现代化的C++调用。

 

5. 完整代码

 

class ThreadPool{  public:    ThreadPool(size_t);    template  <class F, class... Args>    auto enqueue(F &&f, Args &&... args) -> std::future<typename std::result_of<F(Args...)>::type>;    ~ThreadPool();  private:    std::vector<std::thread> workers;    std::queue<std::function<void()>> tasks;    std::mutex queue_mutex;    std::condition_variable condition;    bool stop;};template <class F, class... Args>auto ThreadPool::enqueue(F &&f, Args &&... args)    -> std::future<typename std::result_of<F(Args...)>::type>{    using return_type = typename std::result_of<F(Args...)>::type;    auto task = std::make_shared<std::packaged_task<return_type()>>(        std::bind(std::forward<F>(f), std::forward<Args>(args)...));    std::future<return_type> res = task->get_future();    {        std::unique_lock<std::mutex> lock(queue_mutex);        // don't allow enqueueing after stopping the pool        if (stop)            throw std::runtime_error("enqueue on stopped ThreadPool");        tasks.emplace([task]() { (*task)(); });    }    condition.notify_one();    return res;}inline ThreadPool::ThreadPool(size_t threads)    : stop(false){    for (size_t i = 0; i < threads; ++i)        workers.emplace_back(            [this] {                for (;;)                {                    std::function<void()> task;                    {                        std::unique_lock<std::mutex> lock(this->queue_mutex);                        this->condition.wait(lock,                                             [this] { return this->stop || !this->tasks.empty(); });                        if (this->stop && this->tasks.empty())                            return;                        task = std::move(this->tasks.front());                        this->tasks.pop();                    }                    task();                }            });}inline ThreadPool::~ThreadPool(){    {        std::unique_lock<std::mutex> lock(queue_mutex);        stop = true;    }    condition.notify_all();    for (std::thread &worker : workers)        worker.join();}

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的C11线程池实现,包含基本的初始化、添加任务和销毁线程池的函数: ```c #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <stdatomic.h> // 最大任务数 #define MAX_TASKS 1024 // 任务结构体 typedef struct { void (*func)(void*); // 任务函数指针 void* arg; // 任务参数 } task_t; // 线程池结构体 typedef struct { pthread_t* threads; // 线程数组 atomic_int num_threads; // 线程数 atomic_int num_tasks; // 任务数 atomic_bool shutdown; // 关闭标志 pthread_mutex_t lock; // 互斥锁 pthread_cond_t task_cond; // 条件变量 task_t tasks[MAX_TASKS]; // 任务数组 } thread_pool_t; // 执任务 static void* thread_func(void* arg) { thread_pool_t* pool = (thread_pool_t*)arg; while (1) { // 等待任务 pthread_mutex_lock(&pool->lock); while (pool->num_tasks == 0 && !pool->shutdown) { pthread_cond_wait(&pool->task_cond, &pool->lock); } // 关闭 if (pool->shutdown) { pthread_mutex_unlock(&pool->lock); pthread_exit(NULL); } // 取出任务 task_t task = pool->tasks[--pool->num_tasks]; pthread_mutex_unlock(&pool->lock); // 执任务 task.func(task.arg); } } // 初始化线程池 int thread_pool_init(thread_pool_t* pool, int num_threads) { // 初始化互斥锁和条件变量 if (pthread_mutex_init(&pool->lock, NULL) != 0) { return -1; } if (pthread_cond_init(&pool->task_cond, NULL) != 0) { pthread_mutex_destroy(&pool->lock); return -1; } // 初始化线程数组 pool->threads = (pthread_t*)malloc(num_threads * sizeof(pthread_t)); if (pool->threads == NULL) { pthread_mutex_destroy(&pool->lock); pthread_cond_destroy(&pool->task_cond); return -1; } // 初始化变量 atomic_init(&pool->num_threads, num_threads); atomic_init(&pool->num_tasks, 0); atomic_init(&pool->shutdown, 0); // 创建线程 for (int i = 0; i < num_threads; i++) { if (pthread_create(&pool->threads[i], NULL, thread_func, pool) != 0) { thread_pool_destroy(pool); return -1; } } return 0; } // 添加任务 int thread_pool_add_task(thread_pool_t* pool, void (*func)(void*), void* arg) { if (atomic_load(&pool->shutdown)) { return -1; } // 添加任务 pthread_mutex_lock(&pool->lock); if (pool->num_tasks == MAX_TASKS) { pthread_mutex_unlock(&pool->lock); return -1; } task_t task = { func, arg }; pool->tasks[pool->num_tasks++] = task; pthread_cond_signal(&pool->task_cond); pthread_mutex_unlock(&pool->lock); return 0; } // 销毁线程池 void thread_pool_destroy(thread_pool_t* pool) { if (atomic_exchange(&pool->shutdown, 1)) { return; } // 唤醒所有等待任务的线程 pthread_mutex_lock(&pool->lock); pthread_cond_broadcast(&pool->task_cond); pthread_mutex_unlock(&pool->lock); // 等待所有线程退出 for (int i = 0; i < atomic_load(&pool->num_threads); i++) { pthread_join(pool->threads[i], NULL); } // 释放资源 free(pool->threads); pthread_mutex_destroy(&pool->lock); pthread_cond_destroy(&pool->task_cond); } ``` 使用示例: ```c void task_func(void* arg) { int* num = (int*)arg; printf("Task %d executed by thread %ld\n", *num, pthread_self()); } int main() { thread_pool_t pool; thread_pool_init(&pool, 4); int nums[10]; for (int i = 0; i < 10; i++) { nums[i] = i + 1; thread_pool_add_task(&pool, task_func, &nums[i]); } thread_pool_destroy(&pool); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值