このプロジェクトは、C++11 ベースのスレッド プールです。テンプレート関数のジェネリック プログラミング、std::future、std::packages_task、std::bind、std::forward 完全転送、std::make_shared スマート ポインター、decltype 型推論など、C++ の多くの新機能が使用されていますが、これらに限定されません。 、std::unique_lock およびその他の C++11 の新機能。
このプロジェクトにはある程度の難易度があります。推奨される参考記事シリーズ
C++11実践技術(1) autoとdecltypeの使い方
C++11実践技術(2) std::functionとバインドバインダー
C++11実践技術(3) std::future、std::promise、std::packages_task、async
C++ 演算子キーワードの使用 (オーバーロードされた演算子、ファンクター、型変換演算子)
C++ 標準ライブラリの lock_guard、unique_lock、shared_lock、scoped_lock、recursive_mutex をロックします
コード構造
本プロジェクトのスレッドプール機能は以下の機能に分割して実現しています。
threadpool.init(isize_t num); スレッド数を設定
threadpool::get(TaskFuncPtr& task); タスクキュー内のタスクを読み取る
threadpool::run(); get() を通じてタスクを読み取り、
threadpool.start()を実行する; スレッド プールを開始し、run() を通じてタスクを実行します
threadpool.exec(); タスクをタスク キューにカプセル化します
threadpool.waitForAllDone(); すべてのタスクが実行されるまで待機します
threadpool.stop(); スレッドを分離して解放します想い出
スレッドプール.init
init の機能はスレッド プールを初期化し、主にクラスのメンバー変数にスレッドの数を設定することです。
bool ZERO_ThreadPool::init(size_t num)
{
std::unique_lock<std::mutex> lock(mutex_);
if (!threads_.empty())
{
return false;
}
threadNum_ = num;
return true;
}
threadNum_: init 関数で割り当てられたスレッドの数を保存します。
ここで、unique_lockやlock_guardを用いたロック方法により、自動ロック・ロック解除を実現できます。ただし、unique_lock は一時的にロックを解除したり再ロックしたりできますが、lock_guard はできないため、特殊な場合 (条件変数が使用される場合) には unique_lock を使用する必要があります。(lock_guard は比較的シンプルで、比較的パフォーマンスが良いです)
スレッドプール::get
タスク キュー (実際にはコンシューマ モジュール)からタスクを取得します。
bool ZERO_ThreadPool::get(TaskFuncPtr& task)
{
std::unique_lock<std::mutex> lock(mutex_);
if (tasks_.empty()) //判断任务是否存在
{
//要终止线程池 bTerminate_设置为true,任务队列不为空
condition_.wait(lock, [this] {
return bTerminate_ || !tasks_.empty(); });
}
if (bTerminate_)
return false;
if (!tasks_.empty())
{
task = std::move(tasks_.front()); // 使用了移动语义
tasks_.pop(); //释放资源,释放一个任务
return true;
}
return false;
}
条件変数condition_.wait(lock, [this] { return bTerminate_ || !tasks_.empty(); }); は、条件が完了するまで待ってから終了する必要があります。つまり、タスクが終了するか、タスク キューが空でない場合、条件変数のブロック状態を終了し、次のロジックの実行を継続します。
task = std::move(tasks_.front()); は、移動セマンティクスを使用し、tasks_.front() の内容を task に移動します。コンテンツのコピーを削減できます。移動後はtasks_.front()の内容が不定になるのでそのままポップしてください。
スレッドプール::実行
これがタスクを実行する部分です。get を呼び出してタスク キュー内のタスクを取得し、タスクを実行することも含まれます。
void ZERO_ThreadPool::run() // 执行任务的线程
{
//调用处理部分
while (!isTerminate()) // 判断是不是要停止
{
TaskFuncPtr task;
bool ok = get(task); // 读取任务
if (ok)
{
++atomic_;
try
{
if (task->_expireTime != 0 && task->_expireTime < TNOWMS)
{
//如果设置了超时,并且超时了,就需要执行本逻辑
//超时任务,本代码未实现,有需要可实现在此处
}
else
{
task->_func(); // 执行任务
}
}
catch (...)
{
}
--atomic_;
}
}
}
}
atomic_ : タスクを実行する場合、パラメーターは +1 であり、実行後はパラメーターは -1 です。これは、後でスレッド プールを停止したときに、まだ実行中のタスク (未完了のスレッド) が存在するかどうかを判断するためです。
スレッドプール.スタート
スレッドを作成し、ベクターにスレッド プールを格納します。後でスレッド プールを解放する場合は、スレッドを 1 つずつ解放するのが最善です。
bool ZERO_ThreadPool::start()
{
std::unique_lock<std::mutex> lock(mutex_);
if (!threads_.empty())
{
return false;
}
for (size_t i = 0; i < threadNum_; i++)
{
threads_.push_back(new thread(&ZERO_ThreadPool::run, this));
}
return true;
}
thread_.push_back(new thread(&ZERO_ThreadPool::run, this)); スレッドを作成し、スレッドのコールバック関数が実行されます。
スレッドプール.exec
exec はタスクをタスク キューに保存することです。このコードは、C++ の新機能を多数使用するこのプロジェクトの最も難しい部分です。
/*
template <class F, class... Args>
它是c++里新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数
auto exec(F &&f, Args &&... args) -> std::future<decltype(f(args...))>
std::future<decltype(f(args...))>:返回future,调用者可以通过future获取返回值
返回值后置
*/
template <class F, class... Args>
auto exec(int64_t timeoutMs, F&& f, Args&&... args) -> std::future<decltype(f(args...))>//接受一个超时时间 `timeoutMs`,一个可调用对象 `f` 和其它参数 `args...`,并返回一个 `std::future` 对象,该对象可以用于获取任务执行的结果。
{
int64_t expireTime = (timeoutMs == 0 ? 0 : TNOWMS + timeoutMs); // 根据超时时间计算任务的过期时间 `expireTime`,如果超时时间为 0,则任务不会过期。
//定义返回值类型
using RetType = decltype(f(args...)); // 使用 `decltype` 推导出 `f(args...)` 的返回值类型,并将其定义为 `RetType`(这里的using和typedef功能一样,就是为一个类型起一个别名)。
// 封装任务 使用 `std::packaged_task` 将可调用对象 `f` 和其它参数 `args...` 封装成一个可执行的函数,并将其存储在一个 `std::shared_ptr` 对象 `task` 中。
auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
TaskFuncPtr fPtr = std::make_shared<TaskFunc>(expireTime); // 封装任务指针,设置过期时间 创建一个 `TaskFunc` 对象,并将任务的过期时间 `expireTime` 传递给它。
fPtr->_func = [task]() {
// 具体执行的函数 将封装好的任务函数存储在 `TaskFunc` 对象的 `_func` 成员中,该函数会在任务执行时被调用。
(*task)();
};
std::unique_lock<std::mutex> lock(mutex_);
tasks_.push(fPtr); // 将任务插入任务队列中
condition_.notify_one(); // 唤醒阻塞的线程,可以考虑只有任务队列为空的情况再去notify
return task->get_future();; //返回一个 `std::future` 对象,该对象可以用于获取任务执行的结果。
}
可変個引数テンプレート関数が使用されます。
task_: タスクを保存するためのキュー
condition_.notify_one(): タスクを保存して条件変数を起動します
std::future: タスクを非同期にポイントし、future 機能を使用してタスク関数の戻り結果を取得します。
std::bind: パラメータ リストを関数にバインドして、新しい呼び出し可能オブジェクトを生成します。
std::packages_task: タスクと機能をバインドするテンプレート。これはタスクのカプセル化の一種です。
この関数は汎用プログラミング テンプレート関数を使用しており、タイムアウト時間timeoutMs
、呼び出し可能オブジェクトf
、パラメータの3 つの入力パラメータがありますargs...
。戻り値に接尾辞を付けた std::future オブジェクトを返します。ここで戻り値の接尾辞は、データ型を推測するための decltype(f(args...) の使用を容易にするために使用されます。
auto task = std::make_shared<std::packages_task<RetType()>>(std::bind(std::forward(f), std::forward(args)...)); は渡したタスクですin 関数とパラメータはオブジェクトにバインドされており、関数全体とみなすことができ、その戻り値は RetType 型で、入力パラメータはありません。したがって、パッケージ化およびパッケージ化には std::packages_task<RetType()> のような形式を使用します。カプセル化されたオブジェクトはスマート ポインター (std::make_shared) で管理されます。
同時に、スマート ポインターによって管理される TaskFunc オブジェクトを作成する必要があります。このオブジェクトには 2 つの項目が含まれており、1 つはタイムアウト時間、もう 1 つはカプセル化したタスク オブジェクトです。これら 2 つの項目を
TaskFuncPtr fPtr = std::make_shared(expireTime); および fPtr->_func = task {(*task)();}; の 2 つのコードを通じて渡します。
最後に、タスク関数の実行結果の戻り値が、task->get_future() を通じて返されます。
threadpool.waitForAllDone
すべてのタスクの実行が完了するまで待ちます。
bool ZERO_ThreadPool::waitForAllDone(int millsecond)
{
std::unique_lock<std::mutex> lock(mutex_);
if (tasks_.empty() && atomic_ == 0)
return true;
if (millsecond < 0)
{
condition_.wait(lock, [this] {
return tasks_.empty() && atomic_ == 0; });
return true;
}
else
{
return condition_.wait_for(lock, std::chrono::milliseconds(millsecond), [this] {
return tasks_.empty() && atomic_ == 0; });
}
}
条件変数を使用して、タスクの実行が完了するのを待ちます。タイムアウト実行機能をサポートします。
ここで unique_lock を使用する必要があります。条件変数condition_ はロックが解除され、待機中にスリープ状態になります。lock_guard にはこの操作インターフェイスがありません。
スレッドプール.停止
スレッドプールを終了します。waitForAllDone を呼び出して、すべてのタスクが完了するのを待ってから終了します。
void ZERO_ThreadPool::stop()
{
{
std::unique_lock<std::mutex> lock(mutex_);
bTerminate_ = true;
condition_.notify_all();
}
waitForAllDone();
for (size_t i = 0; i < threads_.size(); i++)
{
if (threads_[i]->joinable())
{
threads_[i]->join();
}
delete threads_[i];
threads_[i] = NULL;
}
std::unique_lock<std::mutex> lock(mutex_);
threads_.clear();
}
joinなどのスレッドの実行が完了するとリターンします。
メイン関数呼び出し
class Test
{
public:
int test(int i) {
cout << _name << ", i = " << i << endl;
Sleep(1000);
return i;
}
void setName(string name) {
_name = name;
}
string _name;
};
void test3() // 测试类对象函数的绑定
{
ZERO_ThreadPool threadpool;
threadpool.init(2);
threadpool.start(); // 启动线程池
Test t1;
Test t2;
t1.setName("Test1");
t2.setName("Test2");
auto f1 = threadpool.exec(std::bind(&Test::test, &t1, std::placeholders::_1), 10);
auto f2 = threadpool.exec(std::bind(&Test::test, &t2, std::placeholders::_1), 20);
cout << "t1 " << f1.get() << endl;
cout << "t2 " << f2.get() << endl;
threadpool.stop();
}
int main()
{
test3(); // 测试类对象函数的绑定
cout << "main finish!" << endl;
return 0;
}
操作結果:
このプロジェクトの完全なコードのダウンロード アドレスは、C++11 のスレッド プールに基づいています。