Thread Facility 03 in muduo Library

Keywords: Mobile

Finally, we introduce the understanding of thread pool encapsulation in muduo library.

  • The most important idea is that thread pools treat threads as the smallest unit of execution that they can add at any time.

  • The entire thread pool object maintains two task queues, threads for the thread pool currently running, and queue for the waiting thread in the storage queue.

  • Thread_uses while loops + conditional variables to determine whether there are vacancies in the current active thread pool and whether new waiting threads enter the thread pool.

  • Thread pools determine the number of threads they will run from the beginning and cannot be changed in subsequent runs.

/***ThreadPool.h***/
class ThreadPool : boost::noncopyable
{
 public:
  typedef boost::function<void ()> Task;//Put threads in thread pool as replaceable tasks and run in thread pool with threads as basic units

  explicit ThreadPool(const string& nameArg = string("ThreadPool"));
  ~ThreadPool();

  // Must be called before start().
  // Set the maximum load that the thread pool runs and the threads that will run in the thread pool
  void setMaxQueueSize(int maxSize) { maxQueueSize_ = maxSize; }//
  void setThreadInitCallback(const Task& cb)
  { threadInitCallback_ = cb; }

  void start(int numThreads);//Start a certain number of threads
  void stop();//Thread pool operation stops

  const string& name() const
  { return name_; }

  size_t queueSize() const;//Return to a queuing thread task

  // Could block if maxQueueSize > 0
  void run(const Task& f);//Put a thread you want to run into the task queue of the thread pool
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  void run(Task&& f);//C++11 Mobile Method for Saving Resources
#endif

 private:
  bool isFull() const;//Determine whether the thread queue is full
  void runInThread();//Functions that really make threads run
  Task take();//Get the first thread of the task queue

  mutable MutexLock mutex_;//mutex
  Condition notEmpty_;//Conditional variable
  Condition notFull_;
  string name_;
  Task threadInitCallback_;//Thread objects executed in thread pools
  boost::ptr_vector<muduo::Thread> threads_;//Thread pool
  std::deque<Task> queue_;//Queuing thread object queues
  size_t maxQueueSize_;//Maximum number of waiting queues
  bool running_;//Has it been started?
};
  • Each thread joining the thread pool has a while loop to ensure that threads in the queue do not wait too long. That is, all threads that will join the thread pool will enter the thread and wait for the queue to be checked.

  • start(): The thread pool startup function ensures that a certain number of threads are started at the time of invocation.

  • stop(): Make sure all running threads stop

  • queueSize(): Returns the number of thread waiting queues at this time to determine whether the thread waiting queue is empty

  • run(): If the thread pool is empty, run the incoming thread directly. If the thread pool waiting queue is full, the current control flow (thread) waits on notFull_; otherwise, the incoming thread is added to the thread waiting queue, and a control flow (thread) blocking the conditional variable is notified by the conditional variable notEmpty_.

  • take(): If the current thread waiting queue is empty and the thread pool is running, the control flow (thread) is blocked on the notEmpty_condition variable. When the conditional variable is activated (threaded object joins the dead thread waiting queue), determine whether a thread object can be pulled out of the thread waiting queue. If so, the conditional variable notFull_notification run() will be blocked in the variable --- which wants to join the queue but the queue has no free location.

  • isFull(): Returns the number of threads in the thread waiting queue to determine whether threads you want to run can be placed in the thread waiting queue.

  • runInThread(): If the thread startup function is not empty, the thread control flow is handed over to the thread object used to initialize the thread pool. When the thread object runs out and the thread pool is still running, the thread pool leaves the initialization mode and enters the circular thread replenishment mode of the thread pool. This mode controls the number of threads in the thread pool: when a new thread object enters the thread pool, the current thread control flow is handed over to the thread object to be executed. That is to say, the thread objects in the thread pool either terminate their `life'on their own initiative, and then the thread objects to be run in the thread pool are determined by the thread replenishment mode of the thread pool. Then take() uses conditional variables to synchronize new threads into the thread pool.

/***ThreadPool.cc***/
ThreadPool::ThreadPool(const string& nameArg)
  : mutex_(),
    notEmpty_(mutex_),
    notFull_(mutex_),
    name_(nameArg),
    maxQueueSize_(0),
    running_(false)
{
}

ThreadPool::~ThreadPool()
{
  if (running_)
  {
    stop();
  }
}

void ThreadPool::start(int numThreads)
{
  assert(threads_.empty());//Start for the first time, asserting that the thread pool is empty
  running_ = true;
  threads_.reserve(numThreads);//Preallocated space
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, "%d", i+1);
    threads_.push_back(new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));
    threads_[i].start();//Start threads directly
  }
  if (numThreads == 0 && threadInitCallback_)//Start only one thread
  {
    threadInitCallback_();
  }
}

void ThreadPool::stop()
{
  {
  MutexLockGuard lock(mutex_);
  running_ = false;
  notEmpty_.notifyAll();//The purpose is to notify notempty conditional variables
  }
  for_each(threads_.begin(),
           threads_.end(),
           boost::bind(&muduo::Thread::join, _1));//Close threads running in thread pool sequentially using STL algorithm
}

size_t ThreadPool::queueSize() const
{//Get the queue length of queued task execution queue
  MutexLockGuard lock(mutex_);
  return queue_.size();
}

void ThreadPool::run(const Task& task)
{
  if (threads_.empty())//If the thread pool is empty, run the thread directly
  {
    task();
  }
  else
  {
    MutexLockGuard lock(mutex_);
    while (isFull())//If the thread waiting queue is full, wait on the notful condition variable
    {
      notFull_.wait();
    }
    assert(!isFull());

    queue_.push_back(task);//Now the thread pool is empty in the task queue
    notEmpty_.notify();//notempty condition variable notification information
  }
}

#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

ThreadPool::Task ThreadPool::take()
{
  MutexLockGuard lock(mutex_);
  // always use a while-loop, due to spurious wakeup
  while (queue_.empty() && running_)//If the thread queue is empty and the thread pool is running
  {//Wait on the notempty condition variable
    notEmpty_.wait();//The current thread stops and waits, and continues to run when the queue is not empty
  }//Then get new tasks
  Task task;//Create a new task
  if (!queue_.empty())
  {
    task = queue_.front();//Get the first task in the queue
    queue_.pop_front();//Head Task in Ejection Queue
    if (maxQueueSize_ > 0)//If the maximum queue length is greater than 0
    {
      notFull_.notify();//Notification threads are ready to run
    }
  }
  return task;//Return task
}

bool ThreadPool::isFull() const
{//Used to determine whether the thread queue is full
  mutex_.assertLocked();
  return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;
}

void ThreadPool::runInThread()//Generate a threadFunc object
{
  try
  {
    if (threadInitCallback_)//If the thread startup function is not empty, start it directly
    {
      threadInitCallback_();
    }
    while (running_)//The while loop ensures that there are no redundant tasks in the thread task waiting queue
    {
      Task task(take());
      if (task)
      {
        task();
      }
    }
  }
  catch (const Exception& ex)   //Anomaly capture process
  {
    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
  }
}

Master's conditional variables use very essence, very essence.

Posted by Yola on Sat, 30 Mar 2019 20:36:29 -0700