0%

怎样实现一个线程池

摘要

本文主要整理了一下如何使用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(); // 做完任务之后执行一下Done
void Wait(); // 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_; }); // 这里会在Wait函数被调用的线程被唤醒的时候做判断,如果lambda函数的结果是false,则继续挂起,否则结束
}

线程池

线程池可以被看成是若干个互不相关的线程,每个线程会定时去获取任务,这是使用触发式的任务启动。
具体实现方式建下面的代码。

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学习中。