非同期スタックとスコープ

構造化された同時実行では、構造化された同時実行とは何か、特に C++ にとってなぜ重要なのかについて説明します。この投稿では、構造化された非同期コードの興味深い特性である非同期スタックと非同期スコープについて説明します。

構造化された同時実行

同時実行は構造化されています 「呼び出し元」関数が再開する前に「呼び出し先」非同期関数が完了したとき。これは、スレッドをブロックせずに実行できます。呼び出し元 (親) は呼び出し先 (子) タスクを起動し、それ自体にハンドルを渡します。それまでは寝ます。」

親が子を起動した直後に、親関数は通常の戻りを行います。多くの場合、非同期タスクをかき回すイベント ループのようなものです。

非同期スタック

親/子の非同期タスクについて話すとき、概念について話しています。 呼び出し元/呼び出し先の関係:現在の操作を実行させた一連の非同期操作があります。この一連の操作は正確に コール スタックに似ていますが、非同期です。実際のプログラム スタックは、これとはまったく異なります。

マルチスレッド アプリケーションをデバッグしたことがある人なら誰でも、実際のプログラム スタックだけでは知りたいことがわからないことを知っています:どうやってここまで来たのか? 一般的に表示されるのは、一部のイベントループが現在特定の機能を処理していることだけです。概念的な非同期スタックが理由を教えてくれます .イベント ループの PoV から、非同期作業が勝手にスケジュールされます。 構造 非同期計算のパフォーマンスは、プログラム実行の上位レベルのプロパティです。

または、現在作成されているマルチスレッド C++ アプリケーションではよくあることですが、そうではありません。 C++20 まで、C++ は構造化された非同期コードを記述するための言語サポートを提供していなかったため、そのコードは通常 非構造化 です。 :親子関係がまったくありません。作業は、アドホックなアウトオブバンド メカニズムを使用して作業を同期し、値とエラーを伝達し、データを維持する、ファイア アンド フォーゲット セマンティクスでスケジュールされます。 jmp でプログラミングするようなものです 関数の代わりに命令 — スタックはまったくありません。

非同期スコープ

C++ プログラマーは、これ以上のものがなかったので、単にこの状況を受け入れてきました。 C++20 がコルーチンを導入するまで、つまり。コルーチンが変革的であるのは、構文が優れているからではなく、非同期スコープを引き起こすからです レキシカルスコープと一致する .

非同期スコープとは非同期スタックが非同期関数のアクティブ化のチェーンである場合、非同期スコープは単一の非同期関数のアクティブ化に対応します。非同期操作とそのネストされたすべての子操作の間存続する必要があるすべての状態 (変数など) を包含します。 .コールバックを使用すると、非同期スコープはばらばらなレキシカル スコープにまたがります。非同期関数が呼び出されたときに開始し、コールバック が呼び出されたときに終了します。 つまり、コードが構造化されている場合です。

非同期コードが構造化されていない場合、親内にネストされた子操作の概念がないため、非同期スコープはまったくありません。または、スコープが重複していると言えます。当然のことながら、これによりリソース管理が困難になります。これが、非同期 C++ に std::shared_ptr が散らばっている理由です。 .

コルーチン

コルーチンに戻ります。コルーチンの場合、非同期スコープは、コルーチンが最初に呼び出されたときに開始され、コルーチンが返されたときに終了します (または co_return と言うべきです)。まあ、それは普通のスコープを持つ普通の関数のようなものです!これがまさにポイントです。

コルーチンによって非同期コードが同期コードのように読み取られることを忘れてください。構文が優れていることを忘れてください。 C++ でのコルーチンの圧倒的な利点は、関数、スコープ、およびリソース管理について既に知っているすべてを活用できるようになったため、非同期スコープをレキシカル スコープと一致させることができることです。この非同期操作が続く限り、データの一部が必要ですか?問題ない。コルーチンのローカル変数にします。

コルーチンを超えて…

コルーチンは、コードで明示することにより、構造化された同時実行の概念を明確にします。 概念について心配する必要はありません スタックとスコープ。1 中括弧の間にスコープがあります。ドロシーがいつでもカンザス州に帰ることができたように、私たちもずっと非同期コードを構築していた可能性があります。

ここにコルーチンに関する汚い秘密があります。それらはコールバックよりも砂糖です。 co_await の後のすべて コルーチンではコールバックです。コンパイラはそうします。そしてくそー、コールバックが永遠にありました 、私たちはそれらを悪用してきました。構造化された同時実行は、これまでわずか 3 回のクリックで完了しました。

言語サポートにより、多く 子操作が親内にネストされるようにする方が簡単ですが、適切なライブラリの抽象化により、C++ で構造化された同時実行がコルーチンなしで完全に可能になり、非常に効率的です。

次の投稿では、C++ 標準提案 P2300 の主題であるこれらのライブラリの抽象化と、ライブラリの抽象化が C++20 コルーチンを超えてもたらすものについて紹介します。

<オール>
  • 実際には、デバッガーがコルーチンを認識し、非同期スタックを表示できるようになるまでは、まだ実行しています。 ↩