摘要
本文主要整理了一下如何使用C++14编写一个属于自己的线程池。
详细代码位于 https://github.com/dxyinme/ezlib/tree/main/thread/threadpool
一些介绍
虽然现在协程在很多情况下已经成为了后台开发的一个主流选择,但是线程以及线程池在整个后台开发的流程中依然很常见。这里介绍一个简单的线程池的使用以及设计编写方法。
设计
任务
我们首先需要一个对任务的定义。一个线程池的作用就是为了并发的去执行一系列不相关的任务的。所以我们定义了如下任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class ThTask { public: ThTask(); ThTask(int task_id); void Done(); void Wait(); virtual void TaskDo() = 0; ~ThTask() = default;
private: std::condition_variable cv_; std::mutex mtx_; bool is_done_; int task_id_; };
|
对于具体的一个任务,我们只需要用一个新的Task类,继承ThTask,实现TaskDo这个函数就可以。而且我们可以利用condition_variable的特性,使得主线程在Wait的时候挂起,直到这个task结束的时候才被唤醒。
上述功能的实现方式如下:
1 2 3 4
| void ThTask::Wait() { std::unique_lock<std::mutex>_(mtx_); cv_.wait(_, [&]{ return is_done_; }); }
|
线程池
线程池可以被看成是若干个互不相关的线程,每个线程会定时去获取任务,这是使用触发式的任务启动。
具体实现方式建下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| for (;;) { std::shared_ptr<ThTask> t_ptr; { std::unique_lock<std::mutex> _(q_mtx_); cv_.wait(_, [&] { return is_done_ || !task_queue_.empty();}); if (is_done_) { break; } t_ptr = task_queue_.front(); task_queue_.pop(); } t_ptr->SetBeginTime(); t_ptr->TaskDo(); t_ptr->Done(); t_ptr->SetFinishTime(); }
|
同样的,只有在commit任务的时候才会触发线程池取任务。为了防止多个线程产生任务争强的情况,每次commit的时候只唤醒一个线程。
1 2 3 4 5
| void ThPool::Commit(std::shared_ptr<ThTask> task_ptr) { task_ptr->SetCommitTime(); task_queue_.push(std::move(task_ptr)); cv_.notify_one(); }
|
总结
这里是一个对线程池的简单介绍和一种实现,当然也有其他的实现方式,TODO学习中。