版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
线程池本质就是一个生产者-消费者模型,它维护一个线程队列和任务队列。一旦任务队列当中有任务,相当于生产者生产了东西,就唤醒线程队列中的线程来执行这些任务。那么,这些线程就相当于消费者线程。
Muduo库的线程数目属于启动时配置,当线程池启动时,线程数目就已经固定下来。
由于Muduo库是基于对象编程的设计理念,所以Muduo库的任务队列中存放的都是回调函数,线程池的另外一种实现方式就是将Muduo库中任务队列换成一个taskbase的基类指针,通过虚函数多态来实现对任务的动态调用,但是如果是这样设计的话,Muduo库就必须提供一个taskbase的基类,外部的使用者就必须继承该基类才能够使用Muduo库。
ThreadPool.h
class ThreadPool : noncopyable
{
public:
typedef std::function<void ()> Task;
explicit ThreadPool(const string& nameArg = string("ThreadPool"));
~ThreadPool();
// Must be called before start().
/* 设置任务队列允许存放的最大任务数量 */
void setMaxQueueSize(int maxSize) { maxQueueSize_ = maxSize; }
/* 设置线程执行前的回调函数 */
void setThreadInitCallback(const Task& cb)
{ threadInitCallback_ = cb; }
/* 启动线程池 */
void start(int numThreads);
/* 关闭线程池 */
void stop();
/* 获取线程池的名称 */
const string& name() const
{ return name_; }
/* 获取任务队列的大小 */
size_t queueSize() const;
/* 运行任务,往线程池当中的任务队列添加任务 */
void run(Task f);
private:
/* 判满 */
bool isFull() const REQUIRES(mutex_);
/* 线程池中线程的执行函数 */
void runInThread();
/* 从任务队列首部获取队列 */
Task take();
/* 与条件变量配合使用的互斥锁 */
mutable MutexLock mutex_;
/* 任务队列queue_不为空了,有任务可以执行了,进而唤醒等待的线程 */
Condition notEmpty_ GUARDED_BY(mutex_);
/* 任务队列queue_不满了,有空间可以使用了,进而唤醒等待的线程 */
Condition notFull_ GUARDED_BY(mutex_);
/* 线程池的名称 */
string name_;
/* 线程执行前的回调函数,即线程初始化函数 */
Task threadInitCallback_;
/* 线程指针数组 */
std::vector<std::unique_ptr<muduo::Thread>> threads_;
/* 任务队列 */
std::deque<Task> queue_ GUARDED_BY(mutex_);
/* 任务队列允许存放的最大任务数量 */
size_t maxQueueSize_;
/* 线程池的运行状态 */
bool running_;
};
ThreadPool.cc
/**************************************************************
* Date:2019-08-01
* Description : 构造函数对成员变量进行初始化,互斥锁条件变
* 量的构造,任务队列的默认任务数量为0,设置线程池运行状态
* 为false。
***************************************************************/
ThreadPool::ThreadPool(const string& nameArg)
: mutex_(),
notEmpty_(mutex_),
notFull_(mutex_),
name_(nameArg),
maxQueueSize_(0),
running_(false)
{
}
/**************************************************************
* Date:2019-08-01
* Description : 析构函数首先要进行线程池的状态判断,若是处于
* 运行状态,那么需要关闭线程,否则不需要做任何操作。
***************************************************************/
ThreadPool::~ThreadPool()
{
/* 如果线程池在运行,那就要进行内存处理,在stop()函数中执行 */
if (running_)
{
stop();
}
}
/**************************************************************
* Date:2019-08-01
* Description : start函数根据参数设置线程池中线程的数量、
* 状态,同时设置线程的回调函数,最后通过start()创建线程。
***************************************************************/
void ThreadPool::start(int numThreads)
{
assert(threads_.empty());
/* 将线程的状态设置为启动状态 */
running_ = true;
/* 给线程池预留numThreads个空间 */
threads_.reserve(numThreads);
/* 在线程池中创建numThreads个线程,无任务时进入睡眠 */
for (int i = 0; i < numThreads; ++i)
{
char id[32];
snprintf(id, sizeof id, "%d", i+1);
/*
* runInThread是每个线程的线程运行函数,线程未执行任务
* 情况下会阻塞
*/
threads_.emplace_back(new muduo::Thread(
std::bind(&ThreadPool::runInThread, this), name_+id));
/* 启动线程 */
threads_[i]->start();
}
if (numThreads == 0 && threadInitCallback_)
{
threadInitCallback_();
}
}
/**************************************************************
* Date:2019-08-01
* Description : stop函数,将线程池的状态修改为停止,同时
* 等待所有的线程结束,
***************************************************************/
void ThreadPool::stop()
{
{
MutexLockGuard lock(mutex_);
/* 将线程池的状态设为停止状态 */
running_ = false;
/* 让阻塞在notEmpty contition上的所有线程执行完毕 */
notEmpty_.notifyAll();
}
for (auto& thr : threads_)
{
/* 对每个线程调用join,实际是调用pthread_join,回收线程资源 */
thr->join();
}
}
/*****************************************************
* Date:2019-08-01
* Description : queueSize()用来获取任务队列的长度
*****************************************************/
size_t ThreadPool::queueSize() const
{
MutexLockGuard lock(mutex_);
return queue_.size();
}
/**************************************************************
* Date:2019-08-01
* Description : run函数将外部的任务放入任务队列,同时在放入
* 任务队列时判断任务队列是否已满,如果已满,就阻塞等待,直到
* 任务队列有空余的空间。
***************************************************************/
void ThreadPool::run(Task task)
{
/* 线程池为空,那么直接由当前线程来执行任务 */
if (threads_.empty())
{
task();
}
else
{
MutexLockGuard lock(mutex_);
/* 若任务队列已满,那么循环等待,直到有空余空间 */
while (isFull())
{
notFull_.wait();
}
assert(!isFull());
/* 将task加入线程池的任务队列,此操作保证任务队列是不满的 */
queue_.push_back(std::move(task));
/* 通知某个等待从queue_中取task的线程 */
notEmpty_.notify();
}
}
/**************************************************************
* Date:2019-08-01
* Description : take函数,线程从线程池中取任务,如果任务队列
* 任务数量为空,那么就阻塞等待,直到有任务进入任务队列。
*
* take函数是每个线程都执行的,需要考虑线程安全,即多线程下
* 取任务的线程安全性,只能串行化
***************************************************************/
ThreadPool::Task ThreadPool::take()
{
/* 使用锁来保证只有一个线程进入,防止惊群效应 */
MutexLockGuard lock(mutex_);
/**
* 若任务队列为空,去循环等待直到任务队列不为空。
* 在wait被唤醒后,还需要在while中去检查条件,这是为了防止“惊群效应”。
* 如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时
* 就必须要再次条件判断,以使只有一个线程进入临界区处理。
* (解锁、等待唤醒、加锁)
*/
while (queue_.empty() && running_)
{
notEmpty_.wait();
}
Task task;
if (!queue_.empty())
{
/* 如果任务队列不为空,那么从队列头部取出任务 */
task = queue_.front();
/* 取出任务后将该任务从队列头部弹出 */
queue_.pop_front();
if (maxQueueSize_ > 0)
{
/*
* 任务取出后任务队列肯定是不满的,这时便可以唤醒未
* 满锁,外部可以通过run来添加新的任务了。
*/
notFull_.notify();
}
}
return task;
}
bool ThreadPool::isFull() const
{
/**
* 调用确保被使用线程锁住,因为isFull函数不是一个线程安全
* 的函数,外部调用要加锁
*/
mutex_.assertLocked();
return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;
}
/**************************************************************
* Date:2019-08-01
* Description : runInThread()是线程运行函数,无任务时都会阻
* 塞在take(),有任务时会争互斥锁。
***************************************************************/
void ThreadPool::runInThread()
{
try
{
if (threadInitCallback_)
{
/* 每个线程运行前执行回调函数,线程初始化函数 */
threadInitCallback_();
}
/* 线程池处于启动状态,会一直循环下去 */
while (running_)
{
/* 从任务队列里取出任务,无任务会阻塞 */
Task task(take());
if (task)
{
/* 如果取出了任务,那么执行该任务 */
task();
}
}
}
catch (const Exception& ex)
{
fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str());
fprintf(stderr, "reason: %s\n", ex.what());
fprintf(stderr, "stack trace: %s\n", ex.stackTrace());
abort();
}
catch (const std::exception& ex)
{
fprintf(stderr, "exception caught in ThreadPool %s\n", name_.c_str());
fprintf(stderr, "reason: %s\n", ex.what());
abort();
}
catch (...)
{
fprintf(stderr, "unknown exception caught in ThreadPool %s\n", name_.c_str());
throw; // rethrow
}
}