muduo之ThreadPool实现及ThreadPool_test测试

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014303647/article/details/88718340

首先我们得明白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。

猜你喜欢

转载自blog.csdn.net/u014303647/article/details/88718340
今日推荐