首先我们得明白ThreadPool类是干嘛的,就是生成一个线程池。是基于Thread类实现的。
首先我们看ThreadPool的数据成员。代码:
mutable MutexLock mutex_;
Condition notEmpty_ GUARDED_BY(mutex_);
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_;
std::vector<std::unique_ptr<muduo::Thread>> threads_; 这个就是线程池的实现,使用vector来存储每个线程的智能指针
string name_; 是线程池的名字,在muduo中默认为ThreadPool
std::deque<Task> queue_ GUARDED_BY(mutex_); 使用双端队列实现的任务队列
mutable MutexLock mutex_;
Condition notEmpty_ GUARDED_BY(mutex_);
Condition notFull_ GUARDED_BY(mutex_); 这个互斥量和条件变量保任务队列的线程安全
size_t maxQueueSize_; 任务队列最多能容纳多少任务
bool running_; 线程池是否运行
知道了数据成员,当然就要看类的构造函数,代码
explicit ThreadPool(const string& nameArg = string("ThreadPool"));
ThreadPool::ThreadPool(const string& nameArg)
: mutex_(),
notEmpty_(mutex_),
notFull_(mutex_),
name_(nameArg),
maxQueueSize_(0),
running_(false)
{
}
没有什么特别的,就是数据的初始化,还没有新建线程池,从ThreadPool_test看,最重要的函数是ThreadPool::start
我们看其源代码:
void ThreadPool::start(int numThreads)
{
assert(threads_.empty());
running_ = true;
threads_.reserve(numThreads);
for (int i = 0; i < numThreads; ++i)
{
char id[32];
snprintf(id, sizeof id, "%d", i+1);
threads_.emplace_back(new muduo::Thread(
std::bind(&ThreadPool::runInThread, this), name_+id));
threads_[i]->start();
}
if (numThreads == 0 && threadInitCallback_)
{
threadInitCallback_();
}
}
这里是最重要的,因为包含着线程池的创建,我们重点看一行代码:
threads_.emplace_back(new muduo::Thread(
std::bind(&ThreadPool::runInThread, this), name_+id));
threads_[i]->start();
我们看这个std::function
ThreadPool::runInThread
然后我们的线程启动了
threads_[i]->start();
线程的启动我们就不具体看了,无非是一些信息的初始化,然后看最重要的一部分代码(muduo/base/Thread.cc):
try
{
func_(); //执行回调函数
muduo::CurrentThread::t_threadName = "finished";
}
所以当线程池里每个线程启动的时候,每个线程都会执行ThreadPool::runInThread
那我们来具体看这个函数到底是何方神圣
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
}
}
哦哦,原来这是一个消费者函数啊,我们的线程去消费任务队列deque里的任务task。
那基本上就清楚了,最后就是任务的生成了,我们看看生产者函数,就是我们的ThreadPool::run
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();
}
}
这个是生产者,那么消费者函数呢?
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;
}
最后我们看看线程池的停止函数
void ThreadPool::stop()
{
{
MutexLockGuard lock(mutex_);
running_ = false;
notEmpty_.notifyAll();
}
for (auto& thr : threads_)
{
thr->join();
}
}
就是将线程池里的每个线程都join掉
基本都是常规操作,实现了一个有界的缓冲区的生产者消费者过程。
最后我们看看测试函数ThreadTest.cc文件。
#include <muduo/base/ThreadPool.h>
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Logging.h>
#include <stdio.h>
#include <unistd.h> // usleep
void print()
{
printf("tid=%d\n", muduo::CurrentThread::tid());
}
void printString(const std::string& str)
{
LOG_INFO << str;
usleep(100*1000);
}
void test(int maxSize)
{
LOG_WARN << "Test ThreadPool with max queue size = " << maxSize;
muduo::ThreadPool pool("MainThreadPool");
pool.setMaxQueueSize(maxSize);
pool.start(5);
LOG_WARN << "Adding";
pool.run(print); // 加任务
pool.run(print); // 加任务
for (int i = 0; i < 100; ++i)
{
char buf[32];
snprintf(buf, sizeof buf, "task %d", i);
pool.run(std::bind(printString, std::string(buf))); //加任务
}
LOG_WARN << "Done";
muduo::CountDownLatch latch(1);
pool.run(std::bind(&muduo::CountDownLatch::countDown, &latch));
latch.wait();
pool.stop();
}
int main()
{
test(0);
test(1);
test(5);
test(10);
test(50);
}
基本就是按照我们源码讲解的,test(maxSize),这个maxSize是任务队列的最大大小。
哦,最后讲一下,
muduo::CountDownLatch latch(1);
pool.run(std::bind(&muduo::CountDownLatch::countDown, &latch));
latch.wait();
这个是主线程等着子线程完成任务。有个小疑问,万一前面的那些任务执行的慢,CountDownLatch::countdown先执行了怎么办?有待解决。。 看了源码,第一这个countdownlatch是为了保证线程池里的线程把任务队列取空,所以有那个latch.wait(),就是保证任务队列被取空,第二stop里面并不会把线程杀死,而是等待join。