C++11 实现优雅的线程池

1,724 阅读1分钟

「这是我参与11月更文挑战的第 3 天,活动详情查看:2021最后一次更文挑战」。

承接上一篇文章 —— 【转载】走进 C++11(三十一) 如何用 60 行实现 C++11 thread pool

对理论和原理感兴趣的话,可以看看。

本文,我完善了线程池的调用代码,并配上了详细代码注释。

完整代码如下所示


#include <iostream>
#include <vector>
#include <queue>
#include <mutex>
#include <functional>
#include <future>

class ThreadPool {
  public:
    ThreadPool(size_t);

    /// @note 可变参模板函数
    /// 尾随的返回类型需要 auto 类型说明符
    /// 这种(尾随返回)类型的写法在 C++ 11 中,用于返回类型依赖实参名或者返回类型复杂的时候
    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> {
    
    /// @note 别名(类型重定义)
    using return_type = typename std::result_of<F(Args...)>::type;

    /// @note 构造打包任务 task ,其中封装了外部传来的函数和对应参数
    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();
    {
        /// @note 上锁
        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");

        /// @note 将封装了 lambda 表达式(调用了 task )的 function 对象存入队列当中
        tasks.emplace([task]() {
            (*task)();
        });

        /// @note 解锁
    }
    condition.notify_one(); ///< 通知一个等待条件变量的子线程

    return res; ///< 返回 future 对象供外部调用
}

inline ThreadPool::ThreadPool(size_t threads)
    : stop(false) {
    for (size_t i = 0; i < threads; ++i)
        workers.emplace_back(
         /// @note 子线程执行的函数
        [this] {
        for (;;) {
            std::function<void()> task;
            {
                /// @note 上锁
                std::unique_lock<std::mutex> lock(this->queue_mutex); 

                this->condition.wait(lock,
                [this] { return this->stop || !this->tasks.empty(); }); ///< 等到收到条件变量的通知并且 lambda 返回的条件(线程池被析构 或者 任务队列不为空)为真才会往下执行

                if (this->stop && this->tasks.empty())
                    return;

                task = std::move(this->tasks.front());  ///< 取任务队列的队首
                this->tasks.pop(); ///< 任务队列出队

                 /// @note 解锁
            }
            task(); ///< 执行任务中的可调用实体
        }
    });
}

inline ThreadPool::~ThreadPool() {
    {
        /// @note 上锁
        std::unique_lock<std::mutex> lock(queue_mutex); 
        stop = true;
        /// @note 解锁
    }
    condition.notify_all(); ///< 通知所有等待条件变量的子线程
    for (std::thread& worker : workers)
        worker.join();
}

/// @note 测试函数
void print() {
    printf("function \n");
}
/// @note 测试函数
void print_float(float a) {
    printf("function: %f\n", a);
}
/// @note 测试函数
int print_int_ret_int(int b) {
    printf("function: %d\n", b);
    return b;
}

int main() {
    /// @note 两个线程的线程池
    ThreadPool tp(2);

    /// <summary>
    /// function 对象
    /// </summary>
    /// <returns></returns>
    std::function<void()> f = print;
    tp.enqueue(f);

    /// <summary>
    /// 无输入参数
    /// </summary>
    /// <returns></returns>
    tp.enqueue(print);

    /// <summary>
    /// 带一个输入参数
    /// </summary>
    /// <returns></returns>
    tp.enqueue(print_float, 1.f);

    /// <summary>
    /// 测试带一个输入参数和一个返回值的函数
    /// </summary>
    /// <returns> 返回 int 类型(future 中 get )</returns>
    auto fu = tp.enqueue(print_int_ret_int, 1);
    int b = fu.get();

    /// <summary>
    ///  测试 lambda 表达式
    /// </summary>
    /// <returns> 返回字符串 </returns>
    auto fu_lambda = tp.enqueue([=](int lines)->std::string {
        std::stringstream ss;
        ss << lines;
        ss << " lines ThreadPool";
        return ss.str(); }, 60);

    std::cout << fu_lambda.get() << std::endl;

}

执行结果如下

image.png