C++ アドバンスト シリーズ カタログ
C++ 演算子キーワードの使用 (オーバーロードされた演算子、ファンクター、型変換演算子)
C++11実践技術(1) autoとdecltypeの使い方
C++11実践技術(2) std::functionとバインドバインダー
C++11実践技術(3) std::future、std::promise、std::packages_task、async
目次
序章
C++ のマルチスレッド命令はスレッドです。例えば
#include <iostream>
#include <thread>
// 线程函数,打印一条消息
void printMessage(const std::string& message) {
std::cout << "Message: " << message << std::endl;
}
int main() {
// 创建线程对象,并指定线程函数及其参数
std::thread t(printMessage, "Hello, world!");
// 等待线程执行完毕
t.join();
return 0;
}
thread を通じて複数のスレッドを作成できます。そして、実行タスク関数は、実行用のコールバック関数の形式でマルチスレッドに渡されます。しかし、スレッドには問題があります:
タスク関数を取得するためのインターフェースがありません。つまり、メインスレッドでタスク関数の戻り値や内部パラメータ値を取得することは困難です。(グローバル変数を通じて取得できますが、同時実行性の一定のリスクがあり、エレガントではありません)。
そして std::future と std::promise はそのようなシナリオを処理できます。
std::future&std::async
スレッド内で std::future オブジェクトを取得したい場合は、後述する std::promise と組み合わせて使用する必要があります。
C++11 では、std::async を使用して、非同期タスク、つまりマルチスレッド実行タスク (スレッドと同じ機能) をより簡単に作成および実行できます。そして、async によって返された結果は std::future で受け取ることができます。
例えば:
#include <iostream>
#include <future>
int calculateSum(int a, int b) {
return a + b;
}
int main() {
std::future<int> result = std::async(calculateSum, 2, 3);
// 执行其他操作...
// 获取异步任务的结果
int sum = result.get();
// 输出结果
std::cout << "Sum: " << sum << std::endl;
return 0;
}
async で作成した非同期タスクは std::future で受信でき、メインスレッドで .get() を使用して戻り値を取得できることがわかります。
注:
result.get() はここでブロックされており、タスクの実行が完了するまで戻り値を取得しません。(スレッドとは異なり、非同期は戻り値を取得した後にリサイクルされます。スレッドは join() を使用する必要があります)。
.get() を含め、さまざまなシナリオで将来の結果を取得するには 3 つの方法があります(興味がある場合は、詳細をご覧ください)。
- get: ブロッキングは、非同期操作が終了するのを待ち、結果を返します。
- wait: 非同期操作が完了するまで待機します。戻り値はありません。
- wait_for: ポーリングは、タスクが完了したことを認識して、将来のステータスを定期的にクエリします。
std::async には 2 つのスレッド作成戦略もあります。
- std::launch::async: async を呼び出すときにスレッドの作成を開始します (デフォルト)。
- std::launch::deferred: 遅延読み込みによりスレッドが作成されます。async が呼び出されたときはスレッドは作成されず、get または wait of future が呼び出されたときにのみスレッドが作成されます。
使用例:
std::future<int> result = std::async(std::launch::async, calculateSum, 2, 3);
std::future<int> result = std::async(std::launch::deferred, calculateSum, 2, 3);
今後の注意事項:
タスクの戻り値を取得するには .get() を使用します。取得できるのは 1 回だけです。複数回取得するとエラーが報告されます。
#include <iostream>
#include <future>
int calculateSum(int a, int b) {
return a + b;
}
int main() {
std::future<int> result = std::async(calculateSum, 2, 3);
// 执行其他操作...
// 获取异步任务的结果
int sum = result.get();
// 输出结果
std::cout << "Sum: " << sum << std::endl;
int sum1 = result.get();//这里第二次使用get会报错
return 0;
}
上記の int sum = result.get(); および int sum1 = result.get(); は、2 回目に使用するとエラーを報告します。
非同期タスクの戻り結果を複数回取得したい場合は、 std::shared_future; を使用できます。
共有未来
Future は、.share() を通じてshared_future オブジェクトを取得できます。
#include <iostream>
#include <future>
// 异步任务函数,返回一个结果
int asyncTask() {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(5));
return 42;
}
int main() {
// 创建异步任务,并获取 std::future 对象
std::future<int> fut = std::async(std::launch::async, asyncTask);
// 创建共享的 std::shared_future 对象
std::shared_future<int> sharedFut = fut.share();
// 在多个线程中获取共享的异步任务结果
std::thread t1([&sharedFut]() {
int result = sharedFut.get();
std::cout << "Thread 1 Result: " << result << std::endl;
});
std::thread t2([&sharedFut]() {
int result = sharedFut.get();
std::cout << "Thread 2 Result: " << result << std::endl;
});
t1.join();
t2.join();
return 0;
}
上記では、fut を使用して共有 std::shared_future オブジェクトsharedFut を作成し、それを 2 つのスレッドに渡します。これら 2 つのスレッドはそれぞれ、sharedFut.get() を呼び出して、共有非同期タスクの結果を取得します。
std::shared_future は、ブロック待機せずに複数のスレッドで同時に結果にアクセスできることに注意してください。これにより、複数のスレッドが非同期タスクの結果を同時にフェッチできるようになります。
std::promise
上記の future は非同期タスクによって返された結果を取得でき、Promise は非同期タスクの中間値を取得できます。あるスレッドで値を生成し、別のスレッドでその値を取得するために使用されます。これにより、あるスレッドで値を設定し、それを std::future に関連付けて、別のスレッドでその値を取得できます。
たとえば、メインスレッドは、非同期スレッドタスクタスクのローカル関数の合計値を取得します。
#include <iostream>
#include <future>
void calculateSum(std::promise<int>& promise, int a, int b) {
int sum = a + b;
// 设置值
promise.set_value(sum);
}
int main() {
std::promise<int> promise;
std::future<int> result = promise.get_future();
// 启动异步任务
std::thread thread(calculateSum, std::ref(promise), 2, 3);//这里用std::async也是一样的,但这里我们不需要获取task的返回值,所以就使用thread。
// 执行其他操作...
// 获取异步任务的结果
int sum = result.get();
// 输出结果
std::cout << "Sum: " << sum << std::endl;
// 等待线程结束
thread.join();
return 0;
}
上記は std::promise オブジェクトの Promise を作成し、promise.get_future() を呼び出してそれに関連付けられた std::future オブジェクトの結果を取得します。次に、新しいスレッドを開始し、calculateSum 関数を引数として渡し、promise オブジェクトと 2 つの整数パラメーターを渡します。
他の操作を実行した後、result.get() を呼び出して非同期タスクの結果を取得します。これにより、タスク task の Promise.set_value(sum) の実行が完了するまで、現在のスレッドがブロックされます。
std::packages_task
std::packages_task は、呼び出し可能オブジェクト(関数、関数オブジェクト、ラムダ式など) をラップし、それを std::future に関連付けて、呼び出し可能オブジェクトを別のスレッドの結果で取得できるようにするためのクラス テンプレートです。
例えば:
#include <iostream>
#include <future>
int calculateSum(int a, int b) {
return a + b;
}
int main() {
std::packaged_task<int(int, int)> task(calculateSum);
std::future<int> result = task.get_future();
// 启动异步任务
std::thread thread(std::move(task), 2, 3);
// 执行其他操作...
// 获取异步任务的结果
int sum = result.get();
// 输出结果
std::cout << "Sum: " << sum << std::endl;
// 等待线程结束
thread.join();
return 0;
}
タスク オブジェクトは、std::packages_task クラス テンプレートを使用して作成され、calculateSum 関数をパラメーターとして渡します。次に、task.get_future() を呼び出して、それに関連付けられた std::future オブジェクトの結果を取得します。
packaged_task と Promise は上記と似ており、Promise は値を保存し、packaged_task は関数を保存します。
std::promise、std::packages_task、std::future の関係
上記のことから、Promise と packaged_task の両方は、操作結果にアクセスするための future を内部に持っているため、.get_future() を使用してオブジェクトを取得できることがわかります。
Promise は値を保存し、packed_task は関数を保存します。特定の値を取得したい場合はpromiseを使用し、戻り値を取得したい場合はpackaging_taskを使用します(上記のようにasyncを使用して戻り値を取得することもできます)。