C++ 同時プログラミング: std::future、std::async、std::packages_task、std::promise の詳細な探索

C++ 同時プログラミング: std::future、std::async、std::packages_task、std::promise の詳細な探索

1. はじめに

1.1 同時プログラミングの概念

同時プログラミングは、プログラムが複数のタスクを同時に処理できるようにすることを核とするコンピューター プログラミング技術です。シングルコア プロセッサでは、一度に 1 つのタスクのみを実行できますが、タスクの切り替えにより、同時実行の効果を生み出すことができます。ただし、マルチコア プロセッサでは、複数のタスクを実際に同時に処理できます。

並行プログラミングの目的は、プログラムの実行効率を向上させることです。特に大量のデータを処理する場合や、大量の計算が必要な場合には、プログラムを同時実行することで実行速度が大幅に向上します。同時に、プログラムの応答性も向上します。実行プロセス中に一部のタスクがブロックされても、他のタスクの実行には影響しません。

同時プログラミングは単純なタスクではなく、データ同期、リソース共有、デッドロックなどの多くの複雑な問題を伴います。したがって、正しく効率的な並行プログラムを作成するには、並行プログラミングのさまざまな概念と手法を深く理解する必要があります。

広く使用されているプログラミング言語として、C++ は同時プログラミング ライブラリの完全なセットを提供します。これらのライブラリには、マルチスレッドのサポート、ミューテックスや条件変数などの同期ツール、およびこの記事で詳しく説明する std::future、std::async、std::packages_task、std::promise などのツールが含まれます。

これらのツールは、並行プログラムをより便利に作成できる強力な機能を提供します。ただし、それらを最大限に活用するには、それらの仕組みと使用方法をしっかりと理解する必要があります。この記事は、読者がこれらの複雑だが強力なツールを理解し、使いこなすことができるようにすることを目的としています。

次に、C++ の同時プログラミングの世界を詳しく調べてみましょう。

1.2 C++ における同時プログラミングの重要性

今日のコンピューティング環境では、プロセッサ コアの数が急速に増加しており、同時プログラミングの重要性がますます高まっています。多くのアプリケーションでは、マルチコア プロセッサの並列コンピューティング機能を最大限に活用しなければ、その潜在的なパフォーマンスを実現できません。

同時プログラミングは、C++ において特に重要な位置を占めています。C++ は、手続き型プログラミング、オブジェクト指向プログラミング、および汎用プログラミングをサポートするマルチパラダイム プログラミング言語です。さらに、C++ は強力な同時プログラミング ライブラリのセットも提供しており、効率的でスケーラブルな同時プログラムを作成できます。

C++ 同時プログラミングの重要性は、次の側面に反映されています。

  • パフォーマンスの向上: マルチコア プロセッサの並列コンピューティング機能を利用することで、より効率的なプログラムを作成できます。このパフォーマンスの向上は、大量のデータが処理される状況や、大量の計算が必要な状況で大幅に向上する可能性があります。
  • 応答性の向上: 多くのアプリケーションでは、長時間実行されるタスクの処理中にユーザー入力への応答性を維持する必要があります。並行プログラミングにより、ユーザー入力を別のスレッドで処理しながら、長時間実行されるタスクを 1 つのスレッドで実行できるため、プログラムの応答性が向上します。
  • 最新のハードウェアを活用する: プロセッサ コアの数が増加するにつれて、将来のプログラムは最新のハードウェアのパフォーマンスを最大限に活用するために並列処理と同時実行を処理できる必要があります。同時プログラミングがこの問題を解決する鍵となります。
  • プログラミング モデルの進化: 同時プログラミングは、最新のプログラミング モデルの重要な部分です。クラウド コンピューティング、ビッグ データ、モノのインターネットなどのテクノロジーの発展に伴い、処理する必要があるデータとコンピューティング タスクの量が増加しているため、これらのニーズを満たすために同時プログラミングを使用する必要があります。

要約すると、C++ 同時プログラミングは、C++ プログラマーが習得すべき重要なスキルです。この記事では、std::future、std::async、std::packages_task、std::promise などの C++ の同時プログラミング ツールについて詳しく説明し、それらを使用して効率的な同時プログラムを作成する方法を例を通して示します。

1.3 std::future、std::async、std::packages_task、および std::promise のガイド (std::future、std::async、std::packages_task、および std::promise の概要)

C++ 同時プログラミングでは、std::future、std::async、std::packages_task、std::promise の 4 つの非常に重要なツールです。これらはすべて C++11 同時プログラミング ライブラリの一部であり、C++14、17、20 およびその他の以降のバージョンではさらに最適化および改善されています。以下に、これら 4 つのツールを簡単に紹介します。

  • std::future : これは、非同期操作の結果を表すテンプレート クラスです。この非同期操作は、別のスレッドで実行されている関数、または計算タスクである可能性があります。std::future は、現在のスレッドをブロックせずに、準備ができた後に結果を取得するメカニズムを提供します。
  • std::async : これは、非同期タスクを開始し、将来このタスクの結果を保持する std::future オブジェクトを返す関数です。std::async は、非同期タスクを実行してその結果を取得する簡単な方法を提供します。
  • std::packages_task : これは、呼び出し可能なオブジェクト (関数やラムダ式など) をカプセル化し、そのオブジェクトの呼び出しの結果を非同期的に取得できるようにするテンプレート クラスです。std::packages_task オブジェクトが呼び出されると、パッケージ化された呼び出し可能オブジェクトが内部で実行され、結果が std::future オブジェクトに保存されます。
  • std::promise : これは、 std::future オブジェクトの結果を手動で設定する方法を提供するテンプレート クラスです。std::promise は、結果を複数の場所で利用できる必要がある非同期タスクがある場合に非常に役立ちます。

これら 4 つのツールは、強力な同時プログラミング モデルを提供します。これにより、コンピューティング タスクを複数のスレッドに分散し、必要に応じてこれらのタスクの結果を取得できます。次の章では、これら 4 つのツールの動作原理と使用方法を詳細に紹介し、実際のプログラムでの使用方法をサンプル コードを通じて示します。

2. std::future: 非同期結果の保存と取得

2.1 std::future の基本原理と構造

並行プログラミングでは、多くの場合、複数のスレッド間でデータを渡す必要があります。std::futureこれは、C++ 標準ライブラリで非同期操作の結果を表すために使用されるクラスで、他のスレッドから計算結果を取得するための非ブロッキング (または非同期) 方法を提供します。

基本的

std::futureその仕組みは単純です。オブジェクトを作成しstd::future、それを別のスレッドに渡します。そのスレッドは、ある時点でstd::promiseORstd::packaged_taskの結果を返します。その後、いつでも呼び出してstd::future::get()結果を取得できます。結果の準備ができていない場合、get()結果が利用可能になるまで現在のスレッドはブロックされます。

std::futureは、テンプレート パラメータがそれが表す非同期操作の結果の型であるテンプレート クラスです。たとえば、std::future<int>結果が整数になる非同期操作を表します。

構造と方法

std::future主に以下のような手法が挙げられます。

  • get(): 非同期操作の結果を取得します。この操作は、結果が準備できるまでブロックされます。このメソッドは内部の結果状態を破壊するため、一度だけ呼び出す必要があります。
  • valid():これにstd::future関連付けられた共有状態があるかどうかを確認します。存在する場合は true を返し、そうでない場合は false を返します。
  • wait(): 非同期操作が完了するまで現在のスレッドをブロックします。
  • wait_for()wait_until(): これら 2 つの関数はタイムアウトを設定するために使用でき、指定された時間内に結果が準備できない場合に戻ります。

std::future基本的な構造は次のとおりです。

方法 説明
get() 非同期操作の結果を取得します。結果の準備ができていない場合は、結果が使用可能になるまでブロックされます。
valid() この にstd::future関連付けられた共有状態があるかどうかを確認します。
wait() 非同期操作が完了するまで現在のスレッドをブロックします。
wait_for() 非同期操作が完了するか、指定された待機時間が経過するまで、現在のスレッドをブロックします。
wait_until() 非同期操作が完了するか、指定された時点に到達するまで、現在のスレッドをブロックします。

基本原理と構造を理解したらstd::future、実際の応用を検討し始めることができます。次のセクションでは、std::future使用シナリオとサンプル コードについて詳しく説明します。

2.2 std::今後の使用シナリオとサンプルコード

std::future の主な使用例は、非同期操作の結果を取得することです。通常、非同期タスクの完了時に結果を取得するために、std::async、std::packages_task、または std::promise とともに使用されます。

std::async で非同期タスクを開始する

この場合、 std::async は非同期タスクを開始するために使用され、非同期タスクの結果を取得するために使用できる std::future オブジェクトを返します。以下に例を示します。

#include <future>
#include <iostream>

int compute() {
    
    
    // 假设这里有一些复杂的计算
    return 42;
}

int main() {
    
    
    std::future<int> fut = std::async(std::launch::async, compute);

    // 在这里我们可以做其他的事情

    int result = fut.get(); // 获取异步任务的结果
    std::cout << "The answer is " << result << std::endl;

    return 0;
}

呼び出し可能オブジェクトを std::packages_task でラップする

std::packages_task は呼び出し可能なオブジェクトをラップし、そのオブジェクトの呼び出しの結果を取得できるようにします。以下に例を示します。

#include <future>
#include <iostream>

int compute() {
    
    
    // 假设这里有一些复杂的计算
    return 42;
}

int main() {
    
    
    std::packaged_task<int()> task(compute);
    std::future<int> fut = task.get_future();

    // 在另一个线程中执行任务
    std::thread(std::move(task)).detach();

    int result = fut.get(); // 获取异步任务的结果
    std::cout << "The answer is " << result << std::endl;

    return 0;
}

std::promise を使用して非同期操作の結果を明示的に設定する

std::promise は、非同期操作の結果を手動で設定する方法を提供します。これは、より詳細な制御が必要な場合や、非同期操作で結果を直接返せない場合に便利です。

#include <future>
#include <iostream>
#include <thread>

void compute(std::promise<int> prom) {
    
    
    // 假设这里有一些复杂的计算
    prom.set_value(42);
}

int main() {
    
    
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    // 在另一个线程中执行任务
    std::thread(compute, std::move(prom)).detach();

    int result = fut.get(); // 获取异步任务的结果
    std::cout << "The answer is " << result << std::endl;

    return 0;
}

上記は std::future の主な使用シナリオといくつかの基本的なサンプルコードです。次のセクションでは、より高度なアプリケーションでの std::future の使用について検討します。

2.3 高度なアプリケーションにおける std::future の適用

std::future は、単純な非同期タスクの結果取得に使用できるだけでなく、さらに重要なことに、複雑な同時並列コードを作成するための基盤を提供します。以下では、高度なアプリケーションにおける std::future の使用法をいくつか紹介します。

非同期操作のチェーン

std::future と std::async を使用して、非同期操作のチェーンを作成できます。このチェーンでは、1 つの操作の出力が次の操作の入力として使用されますが、これらの操作は異なるスレッドで同時に実行できます。

#include <future>
#include <iostream>

int multiply(int x) {
    
    
    return x * 2;
}

int add(int x, int y) {
    
    
    return x + y;
}

int main() {
    
    
    std::future<int> fut = std::async(std::launch::async, multiply, 21);
    
    // 启动另一个异步任务,该任务需要等待第一个异步任务的结果
    std::future<int> result = std::async(std::launch::async, add, fut.get(), 20);
    
    std::cout << "The answer is " << result.get() << std::endl;
    return 0;
}

非同期データ ストリーム パイプライン

std::future を使用して、各ステージが異なるスレッドで同時に実行できる非同期データフロー パイプラインを作成することもできます。

#include <future>
#include <iostream>
#include <queue>

std::queue<std::future<int>> pipeline;

void stage1() {
    
    
    for (int i = 0; i < 10; ++i) {
    
    
        auto fut = std::async(std::launch::async, [](int x) {
    
     return x * 2; }, i);
        pipeline.push(std::move(fut));
    }
}

void stage2() {
    
    
    while (!pipeline.empty()) {
    
    
        auto fut = std::move(pipeline.front());
        pipeline.pop();
        int result = fut.get();
        std::cout << result << std::endl;
    }
}

int main() {
    
    
    std::thread producer(stage1);
    std::thread consumer(stage2);
    producer.join();
    consumer.join();
    return 0;
}

これらは、高度なアプリケーションにおける std::future のほんの一部の例です。実際、std::future は C++ 同時プログラミングの非常に重要な部分であり、さまざまな複雑な同時並列構造を構築するために使用できます。

非同期タスク間の依存関係

特定の順序で実行する必要があるタスクがある場合、 std::future を使用してこの依存関係を実装できます。

#include <future>
#include <iostream>

int task1() {
    
    
    // 假设这是一个耗时的任务
    return 42;
}

int task2(int x) {
    
    
    // 这个任务依赖于task1的结果
    return x * 2;
}

int main() {
    
    
    std::future<int> fut1 = std::async(std::launch::async, task1);
    std::future<int> fut2 = std::async(std::launch::async, task2, fut1.get());
    
    std::cout << "The answer is " << fut2.get() << std::endl;
    return 0;
}

タイムアウト後に非同期タスクをキャンセルする

std::future::wait_for を使用して、タイムアウト後に非同期タスクをキャンセルする機能を実装できます。

#include <future>
#include <iostream>
#include <chrono>

void task() {
    
    
    // 假设这是一个可能会超时的任务
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "Task completed" << std::endl;
}

int main() {
    
    
    std::future<void> fut = std::async(std::launch::async, task);
    
    std::future_status status = fut.wait_for(std::chrono::seconds(2));
    if (status == std::future_status::timeout) {
    
    
        std::cout << "Task cancelled due to timeout" << std::endl;
    } else {
    
    
        std::cout << "Task completed within timeout" << std::endl;
    }
    
    return 0;
}

この例では実際に非同期タスクをキャンセルするのではなく、タスクがタイムアウトした後に待機を停止するだけであることに注意してください。C++ では、実際のタスクのキャンセルはより複雑な問題であり、他の手法を使用する必要があります。

これらの例は、std::future の使用法の一部を示しているだけですが、実際には、std::future を使用して、さまざまな複雑な同時実行性や並列性の問題に対処できます。

3. std::async: 非同期タスクの起動と管理 (std::async: 非同期タスクの起動と管理)

3.1 std::asyncの基本原理と構造 (std::asyncの基本原理と構造)

C++11 によって導入されたstd::async、これは非常に便利な非同期実行メカニズムであり、同時実行と並列操作をより簡単に実装できるようになります。std::async非同期タスクは、渡された開始ポリシーに応じて、すぐに開始することも、実行を遅らせることもできます。この非同期タスクには、関数、関数ポインター、関数オブジェクト、ラムダ式、またはメンバー関数を指定できます。

C++ では、std::async関数はstd::future非同期タスクの結果を取得するために使用できるオブジェクトを返します。非同期タスクが完了すると、std::future::get()関数を通じて結果を取得できます。結果の準備ができていない場合、この呼び出しは結果が準備できるまでブロックされます。

以下はstd::async基本的なプロトタイプです。

template< class Function, class... Args >
std::future<std::invoke_result_t<std::decay_t<Function>, std::decay_t<Args>...>>
    async( Function&& f, Args&&... args );

ここでのパラメータはFunction非同期タスクであり、Argsタスクに渡されるパラメータです。テンプレート パラメータが戻り値の型であるstd::asyncを返します。std::futureFunction

std::async次の 2 つのモードがあります。

  1. 非同期モード ( std::launch::async): 新しいスレッドがすぐに開始され、タスクが実行されます。
  2. 遅延モード ( std::launch::deferred): タスクはfuture::get()、 または がfuture::wait()呼び出されたときに実行されます。

デフォルトのモードでは、std::launch::async | std::launch::deferred新しいスレッドをすぐに開始するか、実行を遅らせるかをシステムが決定します。

std::asyncの主な利点は、結果 ( pass std::future) の必要性をタスクの実行から切り離すことができるため、スレッド管理の詳細を気にすることなく、より柔軟にコードを編成できることです。

3.2 std::async の std::async のユースケースとコード例

std::asyncこれは、並列コンピューティング、バックグラウンド タスク、遅延コンピューティングなどのさまざまなシナリオを処理できる、非同期プログラミングのための強力なツールです。

以下では、いくつかのサンプル コードを使用してその使用法を示しますstd::async

3.2.1 基本的な非同期タスク

以下は、フィボナッチ数列の要素を計算する単純な非同期タスクの例です。

#include <future>
#include <iostream>

int fibonacci(int n) {
    
    
    if (n < 3) return 1;
    return fibonacci(n-1) + fibonacci(n-2);
}

int main() {
    
    
    std::future<int> fut = std::async(fibonacci, 10);
    // 执行其他任务...
    int res = fut.get();  // 获取异步任务的结果
    std::cout << "The 10th Fibonacci number is " << res << "\n";
    return 0;
}

上記のコードでは、フィボナッチ数列の 10 番目の要素を計算する非同期タスクを作成し、他のタスクの実行を続けます。フィボナッチ数の結果が必要な場合は、それを呼び出しますfut.get()。非同期タスクがこの時点で完了している場合は、すぐに結果を取得できます。非同期タスクが完了していない場合、結果が利用可能になるまで呼び出しはブロックされます。 。

3.2.2 非同期実行モードと遅延実行モード

std::async起動戦略を指定する追加の引数を受け入れることができます。以下に例を示します。

#include <future>
#include <iostream>
#include <thread>

void do_something() {
    
    
    std::cout << "Doing something...\n";
}

int main() {
    
    
    // 异步模式
    std::future<void> fut1 = std::async(std::launch::async, do_something);
    std::this_thread::sleep_for(std::chrono::seconds(1));  // 让主线程睡眠1秒
    fut1.get();

    // 延迟模式
    std::future<void> fut2 = std::async(std::launch::deferred, do_something);
    std::this_thread::sleep_for(std::chrono::seconds(1));  // 让主线程睡眠1秒
    fut2.get();

    return 0;
}

上記のコードでは、まず非同期モードでタスクを開始します。このタスクはすぐに新しいスレッドで実行を開始します。次に、別のタスクを遅延モードで開始します。このタスクは、fut2.get()呼び出すまで実行を開始しません。

3.2.3 エラー処理

非同期タスクで例外がスローされた場合、その例外は呼び出し元に伝播されますstd::future::get()これにより、エラー処理が簡単になります。

#include <future>
#include <iostream>

int calculate() {
    
    
    throw std::runtime_error("Calculation error!");
     return 0;  // 这行代码永远不会被执行
}

int main() {
    
    
    std::future<int> fut = std::async(calculate);
    try {
    
    
        int res = fut.get();
    } catch (const std::exception& e) {
    
    
        std::cout << "Caught exception: " << e.what() << "\n";
    }
    return 0;
}

上記のコードでは、非同期タスクが例外calculate()をスローしますstd::runtime_errorこの例外はメインスレッドに伝播され、そこで例外をキャッチして例外メッセージを出力しました。

std::async同時プログラミングと並列プログラミングを処理するためのシンプルかつ安全な方法を提供し、スレッド管理と結果取得の詳細を非表示にするため、実際のタスクに集中できます。

3.3 高度なユースケースにおける std::async の応用

std::async単純な非同期タスクに使用できるだけでなく、一部の高度なアプリケーション シナリオでも役割を果たすことができます。これらのアプリケーションには通常、並列処理を必要とする多数の計算やシナリオが含まれます。

3.3.1 並列アルゴリズム

大量のデータを処理する必要がある場合は、std::asyncアルゴリズムを並列化するために使用できます。たとえば、配列をソートする必要があるとします。配列を半分に分割し、2 つの非同期タスクで半分を別々にソートし、最後に結果を結合します。

並列ソートの例を次に示します。

#include <algorithm>
#include <future>
#include <vector>

template <typename T>
void parallel_sort(std::vector<T>& v) {
    
    
    if (v.size() <= 10000) {
    
      // 对于小数组,直接排序
        std::sort(v.begin(), v.end());
    } else {
    
      // 对于大数组,分成两半并行排序
        std::vector<T> v1(v.begin(), v.begin() + v.size() / 2);
        std::vector<T> v2(v.begin() + v.size() / 2, v.end());

        std::future<void> fut = std::async([&v1] {
    
     parallel_sort(v1); });
        parallel_sort(v2);
        fut.get();

        std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v.begin());
    }
}

この例では、並列ソート関数を定義しますparallel_sort配列のサイズが 10000 未満の場合は、配列を直接ソートします。配列のサイズが 10000 を超える場合は、配列を半分に分割し、最初の半分を 1 つの非同期タスクでソートし、後半を 1 つの非同期タスクでソートします。メインスレッド、そして最後にマージ結果。

3.3.2 バックグラウンドタスク

場合によっては、バックグラウンドでいくつかのタスクを実行する必要があり、完了までに時間がかかることがあります。たとえば、バックグラウンドでファイルをダウンロードしたり、複雑な計算を実行したりする必要がある場合があります。std::asyncこの状況に対処する簡単な方法を提供します。

バックグラウンドでファイルをダウンロードする例を次に示します。

#include <future>
#include <iostream>
#include <string>

std::string download_file(const std::string& url) {
    
    
    // 用你的下载库下载文件...
    return "file content";
}

int main() {
    
    
    std::future<std::string> fut = std::async(download_file, "http://example.com/file");
    // 在此处执行其他任务...
    std::string file_content = fut.get();
    std::cout << "Downloaded file content: " << file_content << "\n";
    return 0;
}

この例では、非同期タスクでファイルをダウンロードし、他のタスクを続行します。ファイルの内容が必要な場合は、fut.get()getresult を呼び出します。

3.3.3 非同期ログシステム

多くのシステムでは、ログ システムはプログラムの動作を記録するために使用される重要なコンポーネントです。ただし、ログへの書き込みは、特に大量のログを書き込む必要がある場合、時間のかかる操作になる可能性があります。を使用するとstd::async、ログ書き込み操作を別のスレッドに配置できるため、メインスレッドのブロックを回避できます。

以下は、非同期ログ システムの簡単な例です。

#include <future>
#include <iostream>
#include <string>

void write_log(const std::string& log) {
    
    
    // 在这里写入日志...
}

void log_async(const std::string& log) {
    
    
    std::async(std::launch::async, write_log, log);
}

int main() {
    
    
    log_async("Start program");
    // 执行其他任务...
    log_async("End program");
    return 0;
}

この例では、非同期タスクでログに書き込み、ログの書き込みが完了するのを待たずにすぐに戻ります。こうすることで、メインスレッドをブロックせずにログに書き込むことができます。

3.3.4 リアルタイムコンピューティングシステム

一部のリアルタイム コンピューティング システムでは、特定の時間内にいくつかのタスクを完了する必要がある場合があり、そうでない場合は、これらのタスクを中止する必要があります。std::asyncそして、std::futureこの要件を達成するための簡単な方法を提供します。

以下はリアルタイム コンピューティング システムの例です。

#include <future>
#include <iostream>
#include <chrono>

int calculate() {
    
    
    // 在这里执行一些复杂的计算...
    return 42;
}

int main() {
    
    
    std::future<int> fut = std::async(std::launch::async, calculate);
    std::chrono::milliseconds span(100);  // 最多等待100毫秒
    if (fut.wait_for(span) == std::future_status::ready) {
    
    
        int result = fut.get();
        std::cout << "Result is " << result << "\n";
    } else {
    
    
        std::cout << "Calculation did not finish in time\n";
    }
    return 0;
}

この例では、非同期タスクで計算を実行し、最大 100 ミリ秒待機します。計算がこの時間内に完了した場合は結果をフェッチしますが、それ以外の場合は計算が時間内に完了しなかったことを示すメッセージを出力します。

4、std::packages_task: 呼び出し可能なターゲットの関数をカプセル化します。

4.1 std::packages_task の基本原理と構造

std::packaged_taskこれは C++11 によって導入されたツールです。その主な機能は、関数、ラムダ式、関数ポインター、関数オブジェクトなどの呼び出し可能なオブジェクトをカプセル化することです。これにより、これらのタスクをさまざまなコンテキストまたはスレッドで実行できるようになります。std::packaged_task非同期操作を抽象化するには、非同期操作に必要なすべての情報が含まれる「パッケージ」とみなすことができます。

基本的

内部的には、std::packaged_taskカプセル化された呼び出し可能オブジェクトをオブジェクトにstd::future関連付けます。std::packaged_taskオブジェクトを呼び出すと、オブジェクトはカプセル化したタスクを実行し、その結果を に保存しますstd::futureこのようにして、std::futureタスクがどのスレッドで完了したかに関係なく、これを通じてタスクの結果を取得できます。

// 创建一个 packaged_task,它将 std::plus<int>() 封装起来
std::packaged_task<int(int, int)> task(std::plus<int>());

// 获取与 task 关联的 future
std::future<int> result_future = task.get_future();

// 在另一个线程中执行 task
std::thread(std::move(task), 5, 10).detach();

// 在原线程中,我们可以从 future 中获取结果
int result = result_future.get();  // result == 15

構造

std::packaged_taskは、テンプレート パラメータが呼び出し可能なオブジェクトの型であるテンプレート クラスです。voidたとえば、 1 つのパラメータを返して受け取る関数がある場合int、 のオブジェクトを作成できますstd::packaged_task<void(int)>

std::packaged_taskこれには主に次のパブリック メンバー関数が含まれます。

  • コンストラクター:std::packaged_taskオブジェクトを構築し、呼び出し可能なオブジェクトを内部にカプセル化するために使用されます。
  • operator(): カプセル化されたタスクを呼び出すために使用されます。
  • valid():std::packaged_taskパッケージ化されたタスクがあるかどうかを確認するために使用されます。
  • get_future():にstd::packaged_task関連付けられたオブジェクトを取得するために使用されますstd::future
  • swap(): 2 つのオブジェクトの内容を交換するために使用されますstd::packaged_task

賢明に使用することでstd::packaged_task、非同期タスクをより適切に管理し、どこからでもタスクの結果を取得できるようになります。これは、C++ 同時プログラミングにおいて非常に便利なツールです。

4.2 std::packages_taskの利用シナリオとサンプルコード

std::packaged_taskマルチスレッド プログラミングには幅広い用途があり、主にタスクを非同期に実行して結果を取得する必要があるシナリオに適しています。std::packaged_task典型的な使用シナリオをいくつか示します。

  1. 非同期タスクの実行: 別のスレッドでタスクを実行し、現在のスレッドで結果を取得する必要がある場合に使用できますstd::packaged_task
  2. タスク キュー: キューを作成しstd::packaged_task、タスクをキューに入れて、1 つ以上のワーカー スレッドでこれらのタスクを実行させることができます。
  3. Future/Promise モデル:結果の取得、タスクの実行、結果の保存に使用されるstd::packaged_taskFuture/Promise モデルを使用できます。std::futurestd::packaged_task

以下にstd::packaged_taskその使用例を示します。

#include <iostream>
#include <future>
#include <thread>

// 一个要在子线程中执行的函数
int calculate(int x, int y) {
    
    
    return x + y;
}

int main() {
    
    
    // 创建一个packaged_task,将calculate函数封装起来
    std::packaged_task<int(int, int)> task(calculate);

    // 获取与task关联的future
    std::future<int> result = task.get_future();

    // 创建一个新线程并执行task
    std::thread task_thread(std::move(task), 5, 10);
    
    // 在主线程中,我们可以从future中获取结果
    int result_value = result.get();

    std::cout << "Result: " << result_value << std::endl;  // 输出: Result: 15

    task_thread.join();

    return 0;
}

この例では、関数をラップするオブジェクトstd::packaged_task作成します。次に、新しいスレッドで実行し、メインスレッドで結果を取得しますこのようにして、タスクを非同期に実行し、必要なときに結果を取得できます。taskcalculatetaskstd::future

4.3 高度なアプリケーションでの std::packages_task の適用

std::packaged_task複雑なマルチスレッド環境には、タスク キュー、スレッド プール、非同期タスク チェーンなどの高度なアプリケーションが数多く存在します。以下にいくつかの応用例を簡単に紹介します。

タスクキュー

タスク キューは、複数のプロデューサー スレッドがタスクを送信し、その後 1 つ以上のコンシューマー スレッドによって実行されるようにする一般的なマルチスレッド設計パターンです。std::packaged_task任意の呼び出し可能なオブジェクトを統一インターフェイスにカプセル化できるため、タスク キューの実装に非常に適しています。

#include <queue>
#include <future>
#include <mutex>

// 任务队列
std::queue<std::packaged_task<int()>> tasks;
std::mutex tasks_mutex;

// 生产者线程
void producer() {
    
    
    // 创建一个packaged_task
    std::packaged_task<int()> task([]() {
    
     return 7 * 7; });

    // 将task添加到任务队列中
    std::lock_guard<std::mutex> lock(tasks_mutex);
    tasks.push(std::move(task));
}

// 消费者线程
void consumer() {
    
    
    // 从任务队列中取出一个task并执行
    std::lock_guard<std::mutex> lock(tasks_mutex);
    if (!tasks.empty()) {
    
    
        std::packaged_task<int()> task = std::move(tasks.front());
        tasks.pop();
        task();
    }
}

スレッドプール

スレッド プールは一般的なマルチスレッド設計パターンであり、一定数のスレッドを作成し、これらのスレッドを再利用してタスクを実行します。std::packaged_taskあるスレッドでタスクを実行し、別のスレッドで結果を取得できるため、スレッド プールでタスクを実装するために使用できます。

非同期タスクチェーン

非同期タスク チェーンは、1 つのタスクの結果が次のタスクの入力として使用される設計パターンです。std::packaged_task完了時に結果をタスクに保存できるため、非同期タスク チェーンの実装に使用できます。std::futureこれは、std::future次のタスクで結果を取得するために使用できます。

// 第一个任务
std::packaged_task<int()> task1([]() {
    
     return 7 * 7; });
std::future<int> future1 = task1.get_future();

// 第二个任务,它的输入是第一个任务的结果
std::packaged_task<int(int)> task2([](int x) {
    
     return x + 1; });
std::future<int> future2 = task2.get_future();

// 在一个线程中执行第一个任务
std::thread(std::move(task1)).detach();

// 在另一个线程中执行第二个任务
std::thread([&]() {
    
    
    task2(future1.get());
}).detach();

// 获取第二个任务的结果
int result = future2.get();

7 * 7この例では、最初のタスクが計算し、2 番目のタスクが結果をインクリメントする非同期タスクのチェーンを作成します。これら 2 つのタスクを 2 つの異なるスレッドで実行します。

次に、メインスレッドで最終結果を取得します。これは、std::packaged_task高度な同時プログラミングにおいて大きな力を発揮します。

機能\モデル タスクキュー スレッドプール 非同期タスクチェーン
該当シーン プロデューサー/コンシューマー モデルに適した、複数のスレッドでタスクを分散して実行する必要がある タスクの実行パフォーマンスを最適化し、スレッドの頻繁な作成と破棄を回避する必要があります。これは、高い同時実行性と大量のタスクを伴うシナリオに適しています。 タスク間には依存関係があり、下流タスクは上流タスクの結果を使用する必要があるため、データ処理や計算負荷の高いタスクに適しています。
リソースの使用量 タスクキューの長さに応じてスレッド数を動的に調整でき、リソースの使用量も柔軟です 事前に作成および再利用される固定数のスレッド、安定したリソース使用量 各タスクは異なるスレッドで実行でき、リソースの使用は柔軟ですが、より多くのスレッド間の同期が必要になる場合があります。
タスク管理 タスクはキューを通じて管理され、先入れ先出しや優先順位などのポリシーに従ってタスクをスケジュールできます。 タスクは通常、スレッド プール内のタスク キューによって管理され、スレッド プールはタスクのスケジューリングと実行を担当します。 タスクの管理はタスク間の依存関係に従って実行する必要があり、通常はより複雑なロジックが必要になります。
結果取得 結果を取得することでstd::future非同期で取得することも、ブロックして結果を待つこともできます 結果を取得することでstd::future非同期で取得することも、ブロックして結果を待つこともできます 結果を取得することでstd::future、非同期で取得することも、ブロックして結果を待つこともでき、上流タスクの結果を下流タスクが直接利用することも可能
エラー処理 通常、エラーはタスクを実行するスレッドで捕捉され、std::future結果を取得するスレッドに渡される必要があります。 通常、エラーはタスクを実行するスレッドで捕捉され、std::future結果を取得するスレッドに渡される必要があります。 通常、エラーはタスクを実行するスレッドで捕捉され、std::future結果を取得するスレッドに渡される必要があります。エラーによりタスク チェーン全体が中断される可能性があります。

5. std::promise: 非同期操作結果の約束 (std::promise: 非同期操作結果の約束)

5.1 std::promiseの基本原理と構造 (std::promiseの基本原理と構造)

std::promise は、C++11 以降のバージョンで導入された同時プログラミング ツールです。これを使用すると、あるスレッドで値または例外を設定し、別のスレッドでこの値または例外を取得できます。このような機能により、std::promise はスレッド間通信の強力な手段になります。

大規模なパーティーを開くときに、ゲストにおいしい料理を提供することを約束する必要があると想像してください。この場合、料理を準備するためにシェフを雇うこともできます。あなたはゲストにおいしい料理を約束すると、シェフはその約束を果たすために舞台裏で最善を尽くします。C++ では、このプロセスは、1 つのスレッド (シェフ) が作業を実行し、別のスレッド (ユーザー) が結果を待つようなものです。

ここで、std::promise の基礎となる原則と構造を詳しく見てみましょう。

基本的

std::promise の背後にある理論的根拠は単純です。std::promise オブジェクトを作成するとき、それに値または例外を与えることができます。値または例外は、Promise に関連付けられた std::future オブジェクトによって取得できます。これは標準的な「生産者-消費者」モデルであり、約束が生産者であり、先物が消費者です。

構造

std::promise は、約束された値の型を表すテンプレート パラメーター T を取るテンプレート クラスです。std::promise オブジェクトは、メンバー関数 set_value を通じて値を設定したり、メンバー関数 set_Exception を通じて例外を設定したりできます。これらの値または例外には、それらに関連付けられた std::future オブジェクトを通じてアクセスできます。

std::promise の簡単な使用例を次に示します。

#include <iostream>
#include <future>
#include <thread>

void my_promise(std::promise<int>& p) {
    
    
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟一些工作
    p.set_value(42); // 设置值
}

int main() {
    
    
    std::promise<int> p;
    std::future<int> f = p.get_future(); // 获取 future
    std::thread t(my_promise, std::ref(p)); // 在新线程中运行函数
    std::cout << "Waiting for the answer...\n";
    std::cout << "The answer is " << f.get() << '\n'; // 获取值
    t.join();
    return 0;
}

この例では、my_promise 関数が新しいスレッドで実行され、promise 値を 42 に設定します。メインスレッドは将来の値を待ってから、それを出力します。メインスレッドは、Promise の値が設定されるまで f.get() でブロックされることに注意してください。

次のセクションでは、std::promise の利用シナリオとサンプルコードを詳しく紹介します。

5.2 std::promise の std::promise のユースケースとコード例

使用するシーン

std::promise の最も一般的な使用例は、マルチスレッド環境でのスレッド間通信です。特に、あるスレッドで値 (または例外) を設定し、別のスレッドでその値 (または例外) を取得する必要がある場合に使用されます。 )時間。

さらに、 std::promise は次のシナリオでも使用できます。

  1. 非同期タスク: 長時間かかる可能性のあるタスクを実行する必要があり、タスクの完了を待ちたくない場合は、 std::promise を使用して新しいスレッドでタスクを実行し、結果を取得できます。メインスレッドで。
  2. データフロー パイプライン: 一連の std::promise オブジェクトと std::future オブジェクトを使用してデータフロー パイプラインを作成できます。各スレッドはパイプラインの一部であり、各スレッドは std::promise オブジェクトを通じてデータを提供し、 std::future オブジェクトを介したデータ。

サンプルコード

std::promise の使用方法を示す例を見てみましょう。この例では、Promise を使用して、新しいスレッドから計算の結果を配信します。

#include <iostream>
#include <future>
#include <thread>

// 这个函数将会在一个新线程中被运行
void compute(std::promise<int>& p) {
    
    
    int result = 0;
    // 做一些计算...
    for (int i = 0; i < 1000000; ++i) {
    
    
        result += i;
    }
    // 计算完成,设置 promise 的值
    p.set_value(result);
}

int main() {
    
    
    // 创建一个 promise 对象
    std::promise<int> p;
    // 获取与 promise 关联的 future 对象
    std::future<int> f = p.get_future();
    // 在新线程中运行 compute 函数
    std::thread t(compute, std::ref(p));
    // 在主线程中获取结果
    std::cout << "The result is " << f.get() << std::endl;
    // 等待新线程完成
    t.join();
    return 0;
}

この例では、時間がかかる可能性のある計算を新しいスレッドで実行し、Promise を使用して計算結果を提供します。メインスレッドでは、future オブジェクトを通じてこの結果を取得します。を呼び出すf.get()と、新しいスレッドが計算を終了して Promise の値を設定するまで、メインスレッドはブロックされます。

5.3 高度なユースケースにおける std::promise の応用

std::promise は、基本的なマルチスレッド プログラミングで使用できるだけでなく、プログラムのパフォーマンスと効率を向上させるために他の同時実行ツールと組み合わせて使用​​するなど、いくつかの高度なアプリケーション シナリオもあります。std::promise を使用する 2 つの高度な使用例を次に示します。

高度なアプリケーション 1: 連鎖した非同期タスク

場合によっては、各タスクの入力が前のタスクの出力に依存する一連の非同期タスクを実行する必要がある場合があります。この場合、Promise と Future のチェーンを作成できます。各タスクには入力 Future と出力 Promise があるため、タスクの実行順序が保証され、各タスクの結果を簡単に取得できます。

たとえば、次のコードは、Promise と Future のチェーンを使用して一連の非同期タスクを実行する方法を示しています。

#include <iostream>
#include <future>
#include <thread>

void chain_task(std::future<int>& f, std::promise<int>& p) {
    
    
    int input = f.get(); // 获取输入
    int output = input * 2; // 执行一些计算
    p.set_value(output); // 设置输出
}

int main() {
    
    
    // 创建 promise 和 future 的链
    std::promise<int> p1;
    std::future<int> f1 = p1.get_future();
    std::promise<int> p2;
    std::future<int> f2 = p2.get_future();

    // 在新线程中运行异步任务
    std::thread t1(chain_task, std::ref(f1), std::ref(p2));
    std::thread t2(chain_task, std::ref(f2), std::ref(p1));

    // 设置初始输入
    p1.set_value(42);

    // 获取最终结果
    std::cout << "The final result is " << f1.get() << std::endl;

    // 等待新线程完成
    t1.join();
    t2.join();

    return 0;
}

高度なアプリケーション 2: 他の同時実行ツールとの組み合わせ

std::promise は、C++ 標準ライブラリの他の同時実行ツール (std::async、std::packages_task、std::thread など) と組み合わせて、より複雑な同時実行パターンを作成できます。

たとえば、 std::async を使用して非同期タスクを開始し、 std::promise を使用してタスクの結果を配信できます。std::packages_task を使用して、新しいスレッドで実行できるタスクをラップし、std::promise を使用してタスクの結果を設定することもできます。この場合、 std::promise はより高度なスレッド間通信メカニズムを提供し、異なるスレッド間でデータと状態を共有できるようにします。

上記は、高度なアプリケーションにおける std::promise の使用シナリオの一部であり、このツールをよりよく理解し、使用するのに役立つことを願っています。次の章では、std::future、std::async、std::packages_task、std::promise の比較と選択について説明します。

6. 並列クラスとスレッドプール

並列ライブラリ

std::futureこれは C++ 標準ライブラリの一部であり、将来他のスレッドで計算される可能性のある値を表します。std::futureそれ自体はスレッド プールに直接関与しません。ただし、std::asyncスレッド プールを利用して非同期タスクを実行できる などのメカニズムと組み合わせて使用​​されることがよくあります。

実際、std::asynca の動作は、それに与えられたパラメータによって異なります。引数が渡された場合std::launch::async、タスクは新しいスレッドで実行されます。引数が渡された場合std::launch::deferred、タスクは呼び出されたstd::future::get()ときに。いずれの場合も、std::async標準ライブラリの実装とシステムの制限に応じて、実装ではスレッド プールが使用される場合があります。

要約すると、std::futureスレッド プールとは直接関係ありませんが、スレッド プールを使用した非同期実行メカニズムで使用できます。

C++の標準ライブラリでは、スレッドプール機能は直接提供されていません。基本的な非同期実行メソッドのみstd::futureを提供するため、C++ 標準ライブラリでは、ワーカー スレッドの数や調整可能なパラメーターなど、スレッド プールの詳細を直接制御することはできません。std::asyncこの制御を実現するには、カスタム スレッド プールを作成するか、既存のオープンソース スレッド プール ライブラリを使用します。

std::packaged_taskスレッド プールでも使用できますが、それ自体はスレッド プールの実装ではありません。std::packaged_taskは、呼び出し可能なオブジェクトを C++ でラップするクラス テンプレートです。これにより、関数を とstd::future組み合わせて。この呼び出し可能オブジェクト (関数、ラムダ式、または関数オブジェクト) が呼び出されると、std::packaged_task結果が保存され、関連付けられたものが準備完了std::futureになります。

std::packaged_taskスレッド プールを使用してタスクを作成し、それらのタスクをスレッド プールに送信できます。これにより、スレッド プールで実行されたタスクは、タスクの結果に非同期アクセスするためのstd::futureオブジェクト。

トレードオフを選択する

スレッド プールは、長時間実行タスクの実行時にスレッド リソースを再利用できることを意味するため、通常、スレッド プールは長時間実行タスクに適しています。このようにして、スレッドの頻繁な作成と破棄によって引き起こされるパフォーマンスの低下を回避できます。スレッド プールを使用すると、特定のパフォーマンス ニーズやシステム制約を満たすために同時スレッドの数を制御することもできます。

短期間で頻度の低いタスクの場合は、std::async並列ライブラリ (C++ 標準ライブラリ、Intel TBB、Microsoft PPL、C++ Boost.Asio ライブラリなど) を使用する方が適切な場合があります。これらのライブラリは、少数のタスクのみを実行する必要がある場合にシンプルなインターフェイスを提供し、スレッド プールの管理がさらに複雑になることを回避できます。通常、並列ライブラリはスレッドの作成と破棄というリソース管理の問題に対処するため、このようなまれなタスクには適しています。

タスクを同時に実行する方法を選択するときは、タスクの性質 (タスクに優先順位があるかどうか、同期する必要があるかどうかなど) と使用するライブラリ (異なる機能と最適化が含まれる) も考慮する必要があることに注意してください。要因として考えられます。

カスタムスレッドプール

単純なカスタム スレッド プールの例を次に示します。

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

class ThreadPool {
    
    
public:
    ThreadPool(size_t num_threads);
    ~ThreadPool();
    void enqueue(std::function<void()> task);

private:
    void worker();

    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex tasks_mutex;
    std::condition_variable tasks_cv;
    bool stop;
};

ThreadPool::ThreadPool(size_t num_threads) : stop(false) {
    
    
    for (size_t i = 0; i < num_threads; ++i) {
    
    
        workers.emplace_back(&ThreadPool::worker, this);
    }
}

ThreadPool::~ThreadPool() {
    
    
    {
    
    
        std::unique_lock<std::mutex> lock(tasks_mutex);
        stop = true;
    }
    tasks_cv.notify_all();
    for (auto &worker : workers) {
    
    
        worker.join();
    }
}

void ThreadPool::enqueue(std::function<void()> task) {
    
    
    {
    
    
        std::unique_lock<std::mutex> lock(tasks_mutex);
        tasks.push(task);
    }
    tasks_cv.notify_one();
}

void ThreadPool::worker() {
    
    
    while (true) {
    
    
        std::function<void()> task;

        {
    
    
            std::unique_lock<std::mutex> lock(tasks_mutex);
            tasks_cv.wait(lock, [this]() {
    
     return !tasks.empty() || stop; });

            if (stop && tasks.empty()) {
    
    
                return;
            }

            task = tasks.front();
            tasks.pop();
        }

        task();
    }
}

上記のカスタム スレッド プールの実装により、スレッド プールのサイズを自由に制御し、タスク キューを管理できます。さらに、Intel TBB、Microsoft PPL、C++ Boost.Asio ライブラリなど、多くのオープン ソース スレッド プール ライブラリから選択できます。これらのライブラリは、マルチスレッド プログラミングのさらなる最適化と高度な制御を提供します。

スレッドプールに関する並列ライブラリのヘルプ

std::thread、std::future、std::async、std::packages_task、std::promise などの C++ の並列クラスを使用してスレッド プールを実装すると、マルチ スレッドの使用率が向上します。 -core プロセッサ: スレッドの作成と破棄のオーバーヘッドを削減し、プログラムの応答性を向上させるのに非常に役立ちます。以下では、これらのクラスがスレッド プールの実装をどのように支援するかについて詳しく説明します。

1.std::スレッド

std::thread は C++ のスレッド ライブラリの基礎であり、スレッドの作成と管理に使用できます。スレッド プールを実装するときは、通常、スレッドのセットを作成し、それらをコンテナー (std::vector など) に保存します。これらのスレッドが作成されると、特定の関数の実行が開始されますが、この関数は通常、タスク キューからタスクを継続的に取得して実行する無限ループになります。

2. std::future と std::promise

std::future および std::promise を使用して、タスクの結果を配信および取得できます。スレッド プールを実装するときは、通常、タスクごとに std::promise オブジェクトを作成し、対応する std::future オブジェクトを呼び出し元に返します。タスクが完了すると、ワーカー スレッドは結果を std::promise オブジェクトに設定し、呼び出し元は std::future オブジェクトを通じて結果を取得できます。

3.std::async

std::async は、非同期タスクを開始して std::future オブジェクトを返すために使用できる単純な非同期プログラミング ツールです。std::async 自体はスレッド プールの実装には適していませんが (常に新しいスレッドが作成されるため)、その設計からスレッド プールのインターフェイスを簡素化する方法を学ぶことができます。具体的には、呼び出し可能なオブジェクトとパラメーターのセットを受け取り、それらをタスクにカプセル化してタスク キューに追加し、std::future オブジェクトを返す std::async に似た関数を提供できます。

4. std::packages_task

std::packages_task は、呼び出し可能オブジェクトをラップするクラスとして見ることができ、呼び出し可能オブジェクトを std::promise オブジェクトにバインドします。std::packages_task オブジェクトが呼び出されると、内部呼び出し可能オブジェクトが呼び出され、結果が std::promise オブジェクトに保存されます。スレッド プールを実装する場合、std::packages_task を使用してタスクをパッケージ化できるため、呼び出し可能なオブジェクトをタスク キューに入れることができる均一な型に変換できます。

これらの並列クラスは、スレッドの作成、タスクの非同期実行、タスク結果の配信などの基本的な機能を提供するため、C++ で効率的なスレッド プールを実装できます。スレッド プールを使用すると、スレッドの数をより適切に制御し、過剰なスレッドの作成と破棄によって生じるオーバーヘッドを回避し、マルチコア プロセッサの使用率を向上させ、プログラムのパフォーマンスを向上させることができます。

クラス名 機能説明 スレッドプールの役割を理解する ユーザープログラミングの観点 実用性
std::スレッド スレッドの作成と管理に使用されます タスクの実行を担当するスレッド プールの基礎 シンプルで使いやすいが、スレッドのライフサイクル管理を手動で行う必要がある 高い
std::未来 非同期タスクの結果を取得するために使用されます タスクの結果を取得する方法を提供します。これにより、呼び出し元はタスクが完了するのを待って結果を取得できます。 非同期タスクの結果を取得する安全かつ簡単な方法を提供します 高い
std::promise 非同期タスクの結果を設定するために使用されます タスクの結果を設定する方法を提供し、ワーカー スレッドがタスクの結果を設定できるようにします。 std::future と組み合わせて使用​​する必要がありますが、使用が少し複雑です 真ん中
std::async 非同期タスクを開始するために使用されます スレッド プールのインターフェイスを簡素化するための設計を学ぶことができます。 非常にシンプルで使いやすいですが、スレッド プールの実装には適していません 真ん中
std::packages_task 梱包作業用 呼び出し可能なオブジェクトはすべてタスクとしてカプセル化できるため、タスクをキューに入れることができます。 タスクの作成と結果の配信は簡素化されますが、ライフサイクルを手動で管理する必要があります 高い

並列ライブラリとスレッドプールの組み合わせ

以下はstd::threadstd::future、 、std::promiseおよびstd::asyncを使用したカスタム スレッド プールの実装です。std::packaged_task

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>

class ThreadPool {
    
    
public:
    // 构造函数: 创建指定数量的工作线程
    // Constructor: creates the specified number of worker threads
    ThreadPool(size_t num_threads);
    
    // 析构函数: 关闭所有线程并释放资源
    // Destructor: stops all threads and releases resources
    ~ThreadPool();

    // 任务入队函数: 将任务添加到任务队列中
    // Enqueue function: adds a task to the task queue
    template <typename F, typename... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;

private:
    // 工作线程执行函数
    // Worker thread execution function
    void worker();

    std::vector<std::thread> workers;             // 工作线程
    std::queue<std::function<void()>> tasks;      // 任务队列
    std::mutex tasks_mutex;                       // 保护任务队列的互斥锁
    std::condition_variable tasks_cv;             // 通知工作线程的条件变量
    bool stop;                                    // 标记线程池是否停止
};

ThreadPool::ThreadPool(size_t num_threads) : stop(false) {
    
    
    for (size_t i = 0; i < num_threads; ++i) {
    
    
        workers.emplace_back(&ThreadPool::worker, this);
    }
}

ThreadPool::~ThreadPool() {
    
    
    {
    
    
        std::unique_lock<std::mutex> lock(tasks_mutex);
        stop = true;
    }
    tasks_cv.notify_all();
    for (auto &worker : workers) {
    
    
        worker.join();
    }
}

template <typename F, typename... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
    
    
    using return_type = typename std::result_of<F(Args...)>::type;

    // 创建 packaged_task,包装任务,将任务与 future 关联
    auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));

    std::future<return_type> result = task->get_future();
    {
    
    
        // 将任务包装为 std::function,并添加到任务队列
        std::unique_lock<std::mutex> lock(tasks_mutex);
        tasks.emplace([task](){
    
     (*task)(); });
    }
    tasks_cv.notify_one();                        // 通知一个工作线程

    return result;
}

void ThreadPool::worker() {
    
    
    while (true) {
    
    
        std::function<void()> task;

        // 从任务队列中获取任务
        {
    
    
            std::unique_lock<std::mutex> lock(tasks_mutex);
            tasks_cv.wait(lock, [this]() {
    
     return !tasks.empty() || stop; });

            // 如果线程池已停止且没有剩余任务,则退出
            if (stop && tasks.empty()) {
    
    
                return;
            }

            task = tasks.front();
            tasks.pop();
        }

        // 执行任务
        task();
    }
}

この実装では、次の重要なセクションに注目してください。

  • コンストラクターはスレッド プールを初期化し、指定された数のワーカー スレッドを作成します。
  • enqueue()この機能はタスクキューにタスクを追加できるタスクエンキューメソッドです。を作成しstd::packaged_taskstd::futureタスクを関連オブジェクトに関連付けます。このメソッドは、呼び出し元が非同期タスクの結果を取得するために使用できるstd::futureオブジェクト。
  • スレッド プール内のワーカー スレッドは待機し、タスク キューからタスクを取得します。タスクが実行されると、タスクに対応するstd::futureオブジェクトが準備完了となり、タスクの結果を取得できるようになります。
  • デストラクターはすべてのワーカー スレッドを停止し、リソースを解放します。

このスレッド プールは、基本的なスレッド管理機能を提供します。また、スレッド数の制御やタスクの優先順位の提供など、必要に応じて他の機能をサポートするように拡張することもできます。

おすすめ

転載: blog.csdn.net/qq_21438461/article/details/130716531