本文地址:(LYanger的博客:http://blog.csdn.net/freeelinux/article/details/52936427)
我们知道线程池本质就是一个生产者消费者模型,它维护一个线程队列和任务队列。一旦任务队列当中有任务,相当于生产者生产了东西,就唤醒线程队列中的线程来执行这些任务。那么,这些线程就相当于消费者线程。
muduo库的线程数目属于启动时配置,当线程池启动时,线程数目就已经固定下来。
它的类图如下:
先上代码,然后分析:
ThreadPool.h
#ifndef MUDUO_BASE_THREADPOOL_H
#define MUDUO_BASE_THREADPOOL_H
#include <muduo/base/Condition.h>
#include <muduo/base/Mutex.h>
#include <muduo/base/Thread.h>
#include <muduo/base/Types.h>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <deque>
namespace muduo
{
class ThreadPool : boost::noncopyable
{
public:
typedef boost::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;
// Could block if maxQueueSize > 0
void run(const Task& f);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
void run(Task&& f);
#endif
private:
bool isFull() const; //判满
void runInThread(); //线程池的线程运行函数
Task take(); //取任务函数
mutable MutexLock mutex_;
Condition notEmpty_; //不空condition
Condition notFull_; //未满condition
string name_;
Task threadInitCallback_; //线程执行前的回调函数
boost::ptr_vector<muduo::Thread> threads_; //线程数组
std::deque<Task> queue_; //任务队列
size_t maxQueueSize_; //因为deque是通过push_back增加线程数目的,所以通过外界max_queuesize存储最多线程数目
bool running_; //线程池运行标志
};
}
#endif
ThreadPool.cc
#include <muduo/base/ThreadPool.h>
#include <muduo/base/Exception.h>
#include <boost/bind.hpp>
#include <assert.h>
#include <stdio.h>
using namespace muduo;
ThreadPool::ThreadPool(const string& nameArg)
: mutex_(),
notEmpty_(mutex_), //初始化的时候需要把condition和mutex关联起来
notFull_(mutex_),
name_(nameArg),
maxQueueSize_(0), //初始化0
running_(false)
{
}
ThreadPool::~ThreadPool()
{
if (running_) //如果线程池在运行,那就要进行内存处理,在stop()函数中执行
{
stop();
} //如果没有分配过线程,那就不存在需要释放的内存,什么都不做就可以了
}
void ThreadPool::start(int numThreads)
{
assert(threads_.empty()); //确保未启动过
running_ = true; //启动标志
threads_.reserve(numThreads); //预留reserver个空间
for (int i = 0; i < numThreads; ++i)
{
char id[32]; //id存储线程id
snprintf(id, sizeof id, "%d", i+1);
threads_.push_back(new muduo::Thread( //boost::bind在绑定类内部成员时,第二个参数必须是类的实例
boost::bind(&ThreadPool::runInThread, this), name_+id));//runInThread是每个线程的线程运行函数,线程为执行任务情况下会阻塞
threads_[i].start(); //启动每个线程,但是由于线程运行函数是runInThread,所以会阻塞。
}
if (numThreads == 0 && threadInitCallback_) //如果线程池线程数为0,且设置了回调函数
{
threadInitCallback_(); //init回调函数
}
}
void ThreadPool::stop() //线程池停止
{
{
MutexLockGuard lock(mutex_); //局部加锁
running_ = false;
notEmpty_.notifyAll(); //让阻塞在notEmpty contition上的所有线程执行完毕
}
for_each(threads_.begin(),
threads_.end(),
boost::bind(&muduo::Thread::join, _1)); //对每个线程调用,pthread_join(),防止资源泄漏
}
size_t ThreadPool::queueSize() const //thread safe
{
MutexLockGuard lock(mutex_);
return queue_.size();
}
//运行一个任务//所以说线程池这个线程池执行任务是靠任务队列,客端需要执行一个任务,必须首先将该任务push进任务队列,等侯空闲线程处理
void ThreadPool::run(const Task& task)
{
if (threads_.empty()) //如果线程池为空,说明线程池未分配线程
{
task(); //由当前线程执行
}
else
{
MutexLockGuard lock(mutex_);
while (isFull()) //当任务队列满的时候,循环
{
notFull_.wait(); //一直等待任务队列不满 //这个锁在take()取任务函数中,取出任务队列未满,唤醒该锁
}
assert(!isFull());
queue_.push_back(task); //当任务队列不满,就把该任务加入线程池的任务队列
notEmpty_.notify(); //唤醒take()取任务函数,让线程来取任务,取完任务后runInThread会执行任务
}
}
#ifdef __GXX_EXPERIMENTAL_CXX0X__
void ThreadPool::run(Task&& task)
{
if (threads_.empty()) //如果线程队列是空的,就直接执行,因为只有一个线程啊
{
task();
}
else
{
MutexLockGuard lock(mutex_);
while (isFull())
{
notFull_.wait();
}
assert(!isFull());
queue_.push_back(std::move(task));
notEmpty_.notify();
}
}
#endif
//take函数是每个线程都执行的,需要考虑线程安全,考虑多线程下取任务的线程安全性,只能串行化
ThreadPool::Task ThreadPool::take() //取任务函数
{
MutexLockGuard lock(mutex_); //注意,必须用锁
// always use a while-loop, due to spurious wakeup //防止惊群效应。
while (queue_.empty() && running_) //如果任务队列为空,并且线程池处于运行态
{ //队列为空时没有使用线程池
notEmpty_.wait(); //等待。条件变量需要用while循环,防止惊群效应。
} //因为所有的线程都在等同一condition,即notempty,只能有线程在wait返回时拿到mutex,并消耗资源
//其他线程虽然被notify同样返回,但资源已被消耗,queue为空(以1个任务为例),其他线程就在while中继续等待
Task task;
if (!queue_.empty()) //从任务队列队头中取任务
{
task = queue_.front();
queue_.pop_front();
if (maxQueueSize_ > 0) //??如果未设置会等于0,不需要唤醒notFull
{
notFull_.notify(); //取出一个任务之后,如任务队列长度大于0,唤醒notfull未满锁
}
}
return task;
}
bool ThreadPool::isFull() const //not thread safe
{
mutex_.assertLocked(); //调用确保被使用线程锁住,因为isFull函数不是一个线程安全的函数,外部调用要加锁
return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_; //因为deque是通过push_back增加线程数目的,所以通过外界max_queuesize存储最多线程数目
}
//线程运行函数,无任务时都会阻塞在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
}
}
muduo库采用的线程池模型实际上是producer和consumer模型,客端通过Thread::run()函数向任务队列push任务,线程池等待处理任务。一旦任务队列not empty,就进行多线程处理,pop front就是从队头开始处理任务。
task(任务)是客端要执行的函数,通过boost::bind注册成为回调函数,放进任务队列当中。