在多線程編程中一般情況下,我們都會使用到線程池,因此muduo庫也有對線程池的封裝,接下來我們看看muduo庫對線程池的實現,基本上和大部分的線程池的實現,大同小異。
ThreadPool.h
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)
#ifndef MUDUO_BASE_THREADPOOL_H
#define MUDUO_BASE_THREADPOOL_H
#include "muduo/base/Condition.h"
#include "muduo/base/Mutex.h"
#include "muduo/base/Thread.h"
#include "muduo/base/Types.h"
#include <deque>
#include <vector>
namespace muduo
{
class ThreadPool : noncopyable
{
public:
typedef std::function<void ()> Task;
explicit ThreadPool(const string& nameArg = string("ThreadPool"));
~ThreadPool();
// Must be called before start().
void setMaxQueueSize(int maxSize) { maxQueueSize_ = maxSize; }
void setThreadInitCallback(const Task& cb)
{ threadInitCallback_ = cb; }
void start(int numThreads);
void stop();
const string& name() const
{ return name_; }
size_t queueSize() const;
// Could block if maxQueueSize > 0
// There is no move-only version of std::function in C++ as of C++14.
// So we don't need to overload a const& and an && versions
// as we do in (Bounded)BlockingQueue.
// https://stackoverflow.com/a/25408989
void run(Task f);
private:
bool isFull() const REQUIRES(mutex_);
void runInThread();
Task take();
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_;
};
} // namespace muduo
#endif // MUDUO_BASE_THREADPOOL_H
muduo庫線程池的成員變量有:
- 互斥鎖mutex,用於線程間數據同步使用
- 兩個條件變量,notEmpty_,notFull_用來判斷任務池中任務的數量
- 線程名稱
- 線程回調函數,用來消費任務
- 線程指針數組
- 任務deque隊列
- 任務隊列的中允許存放的最多任務個數
- 線程是否啓動
muduo ThreadPool框架圖:
ThreadPool.cc
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)
#include "muduo/base/ThreadPool.h"
#include "muduo/base/Exception.h"
#include <assert.h>
#include <stdio.h>
using namespace muduo;
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());
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_();
}
}
void ThreadPool::stop()
{
{
MutexLockGuard lock(mutex_);
running_ = false;
notEmpty_.notifyAll();
}
for (auto& thr : threads_)
{
thr->join();
}
}
size_t ThreadPool::queueSize() const
{
MutexLockGuard lock(mutex_);
return queue_.size();
}
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;
}
bool ThreadPool::isFull() const
{
mutex_.assertLocked();
return maxQueueSize_ > 0 && queue_.size() >= maxQueueSize_;
}
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
}
}
muduo庫線程池的實現:
- 構造函數對成員變量進行初始化,互斥鎖條件變量的構造,任務隊列的默認任務數量爲0;
- 析構函數對線程池的狀態進行判斷,如果線程池不是關閉狀態關閉線程
- start函數設置線程的數量,並設置線程的狀態爲running,並且通過 threads_.reserve(numThreads)設置線程池中線程的數量,同時設置線程的回調函數
- stop函數,將線程池的狀態修改爲停止,同時等待所有的線程結束,看實現代碼,沒有對任務隊列中爲處理任務數量進行判斷,如果停止,應該保證任務隊列中的所有任務都處理結束,而muduo庫的代碼,只是將每個線程手中執行的任務處理完成,因此個人認爲stop函數的實現不是很完美
- run函數將外部的任務放入任務隊列,同時在放入任務隊列時判斷任務隊列是否已滿,如果已滿就阻塞等待,知道任務隊列有空餘的空間
- take函數,線程從線程池中取任務如果任務隊列任務數量爲空,那麼就阻塞等待,知道有任務進入任務隊列
- runInThread函數,線程的工作loop