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.