这两天花时间尝试实现了一下线程池,本来是想完全自己写的,但是写着写着就去参考muduo库的线程池了,实现思路和muduo库的线程池一模一样。我尝试着在不考虑线程安全的情况下对muduo库线程池的实现做一下简述。
1. 核心思想
线程池的关键点在于两点:空闲线程怎么知道任务已经传递进来了,线程间竞争任务
如果我在没有接触muduo库之前,我的想法肯定很简单,直接一个pthread_mutex上手。
然而muduo中使用了条件变量来实现线程池。如果有学过一点进程信号的话,会发现进程信号和条件变量是很像的东西,不同的是,条件变量唤醒的是阻塞住的条件变量。
使用条件变量能很好的避免线程对临界资源的竞争(其实在我看来,条件变量更多得像是一种对底层的封装,因为了解了条件变量的特性之后会发现,如果没有条件变量的话,我自己也会用土方法实现一个类似条件变量的功能)。
2. 两个条件变量
muduo库中使用了两个条件变量:notEmpty, notFull
notEmpty用于通知线程池中的线程不要再阻塞了,试试看从任务列表中获取一个任务。
notFull用于通知给任务的线程:当前任务列表已经没有被塞满了,现在可以放置新的任务到任务列表中(muduo的线程池可以设置任务列表最大值,虽然内部使用的queue来管理任务列表)
条件变量的实际应用场景:
放置任务:
void ThreadPool::run(const Task& task) // 其他线程给线程池塞任务的接口
{
if (threads_.empty()) // 如果线程列表为空,那就只能自己执行了
{
task(); // 执行回调函数(函数指针)
}
else
{
MutexLockGuard lock(mutex_); // 锁住临界区
while (isFull()) // 判断当前任务列表是否已经满了
{
notFull_.wait(); // 如果满了,那就等待任务线程把任务取走后通知当前线程可以放置任务了
}
assert(!isFull());
queue_.push_back(task);
notEmpty_.notify(); // 通知还在阻塞状态的任务线程,现在可以试试看获取任务
}
}
获取任务:
ThreadPool::Task ThreadPool::take() // 线程池中的线程从任务列表中获取任务
{
MutexLockGuard lock(mutex_);
// always use a while-loop, due to spurious wakeup
while (queue_.empty() && running_) // 当任务列表未空的时候才阻塞,不然直接获取任务列表中的任务
{
notEmpty_.wait(); // 唤醒之后马上又会上锁
}
Task task;
if (!queue_.empty())
{
task = queue_.front(); // 获取任务
queue_.pop_front(); // 获取任务之后从任务列列表中将其删除(互斥锁已经将临界资源保护好)
if (maxQueueSize_ > 0)
{
notFull_.notify(); // 取完任务之后,告诉给任务的线程当前任务列表未满(如果给任务的线程卡在等待通知的地方)
}
}
return task; // 将获取到的任务返回给任务线程
}
3. 关闭线程池
当线程池对象要被析构,或者用户想关闭线程池的时候,肯定不能直接析构线程对象,因为当前线程池中的线程对象还在工作,这样肯定不安全。这里简单讲一下陈硕的解决方案。
muduo线程池类中定义了一个bool类型的成员变量:running_,该变量用来告诉线程池中的线程对象现在线程池的状态(而非线程的状态,这里说的线程池状态是指逻辑状态,真实状态依赖具体的线程对象),当running_变为假的时候,就意味着线程对象应该终止循环,退出线程了。
线程池关闭的时候会通知所有线程对象,终止所有线程的等待状态,线程对象终止等待通知的状态后会尝试获取任务列表以及判断当前线程池的逻辑状态(running_),如果running_为假,那么线程对象就会从获取任务的循环中跳出,继而退出线程。
我们看一下muduo线程池的析构函数:
void ThreadPool::stop()
{
{
MutexLockGuard lock(mutex_);
running_ = false; // 改变线程池逻辑状态
notEmpty_.notifyAll(); // 通知所有线程对象来看一眼running_的状态
}
for_each(threads_.begin(),
threads_.end(),
boost::bind(&muduo::Thread::join, _1)); // 挨个等待线程对象退出线程
}
ThreadPool::~ThreadPool()
{
if (running_)
{
stop();
}
}
另外说明一点,muduo线程池使用智能指针来管理线程对象的。