muduo网络库:线程池的实现

线程池问题本质上也是一个生产者-消费者问题

外部线程可以向线程池中的任务队列添加任务,相当于“生产者”;一旦任务队列中有任务,就唤醒线程队列中的线程来执行这些任务,这些线程就相当于“消费者”。模型如下图。

muduo网络库:线程池的实现_第1张图片

muduo ThreadPool类图:

muduo网络库:线程池的实现_第2张图片
  • 任务队列的实现用到了STL的deque容器

deque容器为一个给定类型的元素进行线性处理,像向量一样,它能够快速地随机访问任一个元素,并且能够高效地插入和删除容器的尾部元素。但它又与vector不同,deque支持高效插入和删除容器的头部元素,因此也叫做双端队列。

deque常用函数如下:

#include 

void push_front(const T& x);  // 双端队列头部增加一个元素x

void push_back(const T& x);   // 双端队列尾部增加一个元素x

void pop_front();             // 删除双端队列中最前一个元素

void pop_back();              // 删除双端队列中最后一个元素

void clear();                 // 清空双端队列中最后一个元素

reference at(int pos);        // 返回pos位置元素的引用

reference front();            // 返回手元素的引用

reference back();             // 返回尾元素的引用

bool empty() const;           // 向量是否为空,若为true,则向量中无元素
  • 几个成员函数的说明
//文件名:ThreadPool.cc

// 启动线程池,启动的线程是固定个数的(numThreads)
void ThreadPool::start(int numThreads)
{
  assert(threads_.empty()); // 断言线程池是空的
  running_ = true; // 运行状态标记置为true
  threads_.reserve(numThreads); // 为线程池预留指定大小的空间
  // 创建线程
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, "%d", i);
    threads_.push_back(new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));
    threads_[i].start();
  }
}

在线程池 start 的时候,使用new muduo::Thread(boost::bind(&ThreadPool::runInThread, this), name_+id)初始化,其中 boost::bind 是函数类型转换的函数,目的是将 ThreadPool::runInThread 转换为 void() ,这里 ThreadPool::runInThread 其实已经是 void() 函数了,为了使得程序兼容性更好,这么做可以使得细节更加完美。最后对每个线程执行 start() 函数,

void ThreadPool::runInThread()
{
  try
  {
    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
  }
}

runInThread() 函数先通过当前线程的 running 的状态进行判断,然后执行 take() 取出一个任务,如果取出来了执行 take() 。 take() 函数如下:

ThreadPool::Task ThreadPool::take()
{
  MutexLockGuard lock(mutex_);
  // always use a while-loop, due to spurious wakeup
  while (queue_.empty() && running_)
  {
    cond_.wait();	//条件变量用以当前队列为空时的等待
  }
  Task task;
  if(!queue_.empty())
  {
    task = queue_.front();
    queue_.pop_front();
  }
  return task;
}

接下来是,线程池的任务添加:

void ThreadPool::run(const Task& task)
{
  if (threads_.empty())
  {
    task();
  }
  else
  {
    MutexLockGuard lock(mutex_);
    queue_.push_back(task);
    cond_.notify();
  }
}

最后线程池中的线程销毁:

void ThreadPool::stop()
{
  {
  MutexLockGuard lock(mutex_);
  running_ = false;
  cond_.notifyAll();
  }
  for_each(threads_.begin(),
           threads_.end(),
           boost::bind(&muduo::Thread::join, _1));
}

通过上述代码可以看出这是一个固定大小的线程池,实现比较容易,将生产者-消费者模型的产品抽象成任务,从线程池中添加( run() ),同时利用线程池中的线程将这些任务完成。

最后任务什么时候截止呢?线程池什么时候才能执行 stop() ?

线程池的结束不是库的设计者能够决定呢,这是开发者(用户)决定的,让目前进行的线程池停止,必须保证当前所有的任务已经全部完成,从这个角度出发,我们可以将最后一个任务设计成一个结束的任务标志,怎么设计呢?这就可以采用CountDownLatch 了。CountDownLatch 是一个倒计时类:它的用途有:
(1)主线程发起多个子线程,等这些子线程各自都完成一定的任务之后,主线程才继续执行。通常用于主线程等待多个子线程完成初始化。
(2)主线程发起多个子线程,子线程都等待主线程,主线程完成其他一些任务之后通知所有子线程开始执行。通常用于多个子线程等待主线程发出“起跑”命令。

//设置结束标志
  muduo::CountDownLatch latch(1);
  pool.run(boost::bind(&muduo::CountDownLatch::countDown, &latch));
  latch.wait();
  pool.stop();

你可能感兴趣的:(muduo网络库)