C++11简易线程池实现

C++11简易线程池实现

作者:tsing

本文地址:https://www.cnblogs.com/TssiNG-Z/p/17073115.html

简介

C++11版本的通过条件变量以及任务队列所实现的一种不等待任务完成的简易线程池

正文

参考<<C++并发编程实战>>并对书中实现做了轻微增改, 主要添加了控制线程池启停的相关逻辑.

体量很小, 直接上源码


/**
 * 在本例中, 假定线程池中的线程固定处理一个以void为返回值
 * 接受一个void*为参数的函数对象.
 * 
 * 此处简单对函数对象的类型声明做一个美化
 */
using func_obj           = std::function<void(void*)>;

/** 类型声明美化 */
using std_section_lock   = std::lock_guard<std::mutex>;

/** 类型声明美化 */
using std_unique_section = std::unique_lock<std::mutex>;

/**
 * 线程池任务对象, 包含一个函数对象和函数对象所需入参
 * 
 * func_obj : 函数对象, 线程工作入口
 * 
 * void*    : 函数入参, void*类型方便对接不同的参数
 */
typedef struct _st_task_obj_
{
    func_obj f_;
    void*    p_;
}task_obj;

/**
 * 线程池类, 大致工作思路如下 :
 * 1. 在线程池对象初始化时, 通过入参配置线程数
 * 2. 线程池内部包含一个接收 task_obj 对象的任务队列, 通过submit接口向该队列推送task
 * 3. 线程池中的线程会被任务队列相关的condition_variable唤醒并从中获取task_obj对象以执行
 */
class ThreadPool
{
public:
    /** 单参构造, 置启停标志为false并通过入参创建指定数量的线程对象, 加入线程列表中 */
    ThreadPool(size_t thread_cnt)
        : stop_(false)
    {
        for (size_t cnt = 0; cnt < thread_cnt; ++cnt)
        {
            threads_.push_back(std::thread(&ThreadPool::do_task, this, cnt));
        }
    }

    /** 析构, 置启停标志为true, 唤醒所有线程, 顺序等待所有线程退出 */
    ~ThreadPool()
    {
        stop_ = true;
        task_cond_.notify_all();

        for (size_t idx = 0; idx < threads_.size(); ++idx)
        {
            if (threads_[idx].joinable()) threads_[idx].join();
        }
    }

    /**
     * 此类中已经包含了不允许拷贝的成员变量, 所以编译器默认是会禁用掉拷贝相关接口的.
     * 现代编译器已经能做到相当强的优化了, 但有个好习惯在学习阶段我认为是很重要的.
     * 
     * P.S 关于拷贝赋值为什么要返回当前对象的引用, 请参看 <<Effective C++>> 条款10.
     */
    ThreadPool() = delete;
    ThreadPool(const ThreadPool &other) = delete;
    ThreadPool& operator= (const ThreadPool &other) = delete;

private:

    /** 线程列表 */
    std::vector<std::thread> threads_;

    /** 任务队列 */
    std::queue<task_obj> tasks_;
    
    /** 任务队列互斥量 */
    std::mutex task_mtx_;

    /** 任务队列触发条件变量 */
    std::condition_variable task_cond_;

    /** 线程池启停标志 */
    std::atomic_bool stop_;

public:
    /**
     * 1.提交一个函数对象和一个入参
     * 2.使用入参创建task对象并将其推如任务队列
     * 3.触发一次任务唤醒事件
     */
    template<typename FuncType>
    void submit(FuncType func, void *param)
    {
        {
            std_section_lock lk(task_mtx_);
            task_obj t;
            t.f_ = func;
            t.p_ = param;
            tasks_.push(t);
        }
        task_cond_.notify_one();
    }

private:
    /**
     * 1.在线程池运行期间, 每个线程等待任务队列的唤醒事件
     * 2.定时检查线程池是否停止
     * 3.若被条件队列唤醒且仍处于运行期, 则从任务队列中取出task并执行
     */
    void do_task(size_t id)
    {
        while (!stop_)
        {
            task_obj task;
            {
                std_unique_section lk(task_mtx_);
                if (task_cond_.wait_for(lk, std::chrono::milliseconds(5),
                    [this](){ return !tasks_.empty() || stop_; }))
                {
                    if (!stop_)
                    {
                        printf("thread %d get task\n", id);
                        task = tasks_.front();
                        tasks_.pop();
                    }
                }
            }

            if (task.f_)
                task.f_(task.p_);
            else
                std::this_thread::yield();
        }
        printf("thread %d stop\n", id);
    }
};

参考文献:

  1. <<C++并发编程实战>>

以上, 如有错误疏漏或疑问, 欢迎指正讨论, 转载请注明.

posted @ 2023-01-29 16:40  public_tsing  阅读(72)  评论(0编辑  收藏  举报