基于C++11实现线程池

本文详细介绍了如何使用C++11特性逐步实现线程池,包括线程池原理、任务队列(Task Queue)和线程池(Thread Pool)的实现。通过线程安全的SafeQueue和内置工作线程类,实现任务的提交和执行,讨论了C++11的并发编程工具,如std::thread和std::async。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这篇总结会将重心放在C++11的语法糖上,对于C++11的并发编程部分(std::threadstd::future等)将仅进行最简洁最必要的阐述。有关并发编程部分可以移步至几篇大佬总结的比较好的博文中进行补充学习:

  1. 《C++并发编程(从C++11到C++17)》:

https://paul.pub/cpp-concurrency​paul.pub/cpp-concurrency

2. 《从pthread转换到std::thread》:

从 pthread 转换到 std::thread​segmentfault.com/a/1190000002655852正在上传…重新上传取消

3. 《货比三家:C++中的task based并发》:

货比三家:C++ 中的 task based 并发​segmentfault.com/a/1190000002706259正在上传…重新上传取消

0x01 逐步实现线程池

线程池原理

C++11加入了线程库,从此告别了标准库不支持并发的历史。然而C++对于多线程的支持还是比较低级,稍微高级一点的用法都需要自己去实现,比如线程池、信号量等。线程池(thread pool)这个东西,一般在面试时的回答都是:“管理一个任务队列,一个线程队列,然后每次去一个任务分配给一个线程去做,循环往复。”这回答貌似没有问题,但是写起程序来的时候就出问题了。

有什么问题?线程池一般是要复用线程,所以如果是取一个task分配给某一个thread,执行完之后再重新分配,在语言层面这是基本不能实现的:C++的thread都是执行一个固定的task函数,执行完之后线程也就结束了。所以该如何实现task和thread的分配呢?

让每一个thread创建后,就去执行调度函数:循环获取task,然后执行。

这个循环该什么时候停止呢?

很简单,当线程池停止使用时,循环停止。

这样一来,就保证了thread函数的唯一性,而且复用线程执行task。

总结一下,我们的线程池的主要组成部分有二:

  • 任务队列(Task Queue)
  • 线程池(Thread Pool)

线程池与任务队列之间的匹配操作,是典型的生产者-消费者模型,本模型使用了两个工具:一个mutex + 一个条件变量。mutex就是锁,保证任务的添加和移除(获取)的互斥性;一个条件变量保证多个线程获取task的同步性:当任务队列为空时,线程应该等待(阻塞)。

接下来我们就可以逐渐将一块块积木拼成一个完整的简易线程池。

积木1:任务队列(Task Queue)

我们会理所当然地希望任务以发送它相同的顺序来逐个执行,因此队列是最适合的数据结构。

这里我们把任务队列单拿出来,独自为类,方便以后进行各种骚操作。

将任务队列单拿出来之后,我们应考虑一个问题:正如上一节提到的线程池task与thread的分配方法所示,线程池中的线程会持续查询任务队列是否有可用工作。当两个甚至多个线程试图同时执行查询工作时,这会引起难以估计的灾难。因而我们需要对C++的std::queue进行包装,实现一个线程安全SafeQueue

实现一个线程安全的SafeQueue原理很简单,利用mutex来限制并发访问即可。我们可以在SafeQueue类中定义一个std::mutex类型的成员变量,并在相应的操作接口(如入队接口enqueue())中利用互斥体包装器来管理这个mutex,确保没有其他人正在访问该资源。

下面给出完整的SafeQueue代码:

template <typename T>
class SafeQueue
{
private:
    std::queue<T> m_queue; //利用模板函数构造队列
​
    std::mutex m_mutex; // 访问互斥信号量
​
public:
    SafeQueue() {}
    SafeQueue(SafeQueue &&other) {}
    ~SafeQueue() {}
​
    bool empty() // 返回队列是否为空
    {
        std::unique_lock<std::mutex> lock(m_mutex); // 互斥信号变量加锁,防止m_queue被改变
​
        return m_queue.empty();
    }
​
    int size()
    {
        std::unique_lock<std::mutex> lock(m_mutex); // 互斥信号变量加锁,防止m_queue被改变
​
        return m_queue.size();
    }
​
    // 队列添加元素
    void enqueue(T &t)
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_queue.emplace(t);
    }
​
    // 队列取出元素
    bool dequeue(T &t)
    {
        std::unique_lock<std::mutex> lock(m_mutex); // 队列加锁
​
        if (m_queue.empty())
            return false;
        t = std::move(m_queue.front()); // 取出队首元素,返回队首元素值,并进行右值引用
​
        m_queue.pop(); // 弹出入队的第一个元素
​
        return true;
    }
};

积木2:线程池(Thread Pool)

线程池是线程池模型的主体,我们将它拆成更小的部分来逐步分析,方便理解。

2-1 提交函数

线程池最重要的方法就是负责向任务队列添加任务。我们的提交函数应该做到以下两点:

  • 接收任何参数的任何函数。(普通函数,Lambda,成员函数……)
  • 立即返回“东西”,避免阻塞主线程。这里返回的“东西”或者说“对象”应该包含任务结束的结果。

完整的提交函数如下所示:

// Submit a function to be executed asynchronously by the pool
    template <typename F, typename... Args>
    auto submit(F &&f, Args &&...args) -> std::future<decltype(f(args...))> ①
    {
        // Create a function with bounded parameter ready to execute
        std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); ②// 连接函数和参数定义,特殊函数类型,避免左右值错误
​
        // Encapsulate it into a shared pointer in order to be able to copy construct
        auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);  ③
​
        // Warp packaged task into void function
        std::function<void()> warpper_func = [task_ptr]()
        {
            (*task_ptr)();
        };  ④
​
        // 队列通用安全封包函数,并压入安全队列
        m_queue.enqueue(warpper_func);
​
        // 唤醒一个等待中的线程
        m_conditional_lock.notify_one();  ⑤
​
        // 返回先前注册的任务指针
        return task_ptr->get_future();
    }

C++11众多的语法糖正式来袭。下面讲一下需要注意的地方:

1.submit()是一个模板函数,这很明显。template<typename F, typename... Args>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值