どこでもコルーチン

C ++ Russia 2016のレポートで Gor Nishanovは、コルーチンはC ++ランタイムがない場合でも、どの環境でも機能できると述べました。 そのような環境でコルーチンを試してみたかった。 標準ライブラリにコルーチンサポートを最初から実装する方法をご覧ください。 コルーチンが例外なくどのように生きるか、およびオペレーティングシステム(ベアメタル)の外で動作するかどうかを確認します。





コンパイラーのサポート



Microsoftのコンパイラ-cl.exeを使用しました。 Visual Studio 2015以降、コンパイラーはコルーチンの実験的サポートを備えています。 コルーチン(コルーチン、コルーチン)の使用を開始するには、コンパイラに/ awaitキーを渡す必要があります。



cl.exeでのコルーチンの実装方法



コルーチンはコンパイラレベルでサポートされますが、コンパイラは、コルーチンの作成、一時停止、再開を制御できる組み込み関数のセットを提供します。

extern "C" size_t _coro_resume(void *); extern "C" void _coro_destroy(void *); extern "C" size_t _coro_done(void *); #pragma intrinsic(_coro_resume) #pragma intrinsic(_coro_destroy) #pragma intrinsic(_coro_done)
      
      







原則として、プログラマーが独自のコルーチンを実装するには、これらの関数のプロトタイプで十分です。



標準ライブラリの実装要件



コンパイラがco_await EXPRのような構造に遭遇した場合、次のメソッドを実装するにはEXPR式タイプが必要です。



 bool await_ready(); void await_suspend(std::experimental::coroutine_handle<promise_type> coroutineHandle); value_type await_resume();
      
      







つまり、タイプがAwaitableコンセプトを満たすために(詳細については、 こちらを参照してください



C ++ランタイム要件



これが最も興味深い部分のある場所です;コンパイラは次の演算子を必要とします:

 void operator delete(void* location, size_t); void * operator new(size_t size);
      
      







またはオペレーターの存在

 void operator delete(void* location, size_t); void * operator new(size_t size, std::nothrow_t const &);
      
      







コンパイラは、コルーチンを完全にサポートするために何も必要としません!



new演算子とdelete演算子の上記の署名から、コルーチンは例外を持たないさまざまな環境にちょうど適しているthrowingオプションnewとnothrowオプションnewの両方で機能することがわかります!



coroutine_handleとは



メソッドの1つ(await_suspend)にcoroutine_handle型が必要であることがわかりました。これはどのような型ですか?

実際、coroutine_handleはvoid *のC ++ラッパーです。さらに、この型はポインターのセマンティクスに近いセマンティクスを持ち、割り当て、コピーが可能です。そして最も重要なことは、プログラマーがこのオブジェクトを破棄する責任があります(メモリの削除と類似しています)ポインターが指すこと)。

このタイプのオブジェクトは、コルーチンの実行と完了を担当します。

coroutine_handleから必要なメソッドはほとんどありません。



 static coroutine_handle from_address(void *_Addr) noexcept; void *address() const noexcept; void operator()() const noexcept { resume(); } void resume() const; void destroy(); bool done() const;
      
      







from_addressメソッドにも言及する必要があります-このメソッドは、void *からオブジェクトを取得するように設計されています。なぜこれが必要なのですか? 通常、非同期操作では、コールバックを行うときにコンテキストを設定できるため、coroutine_handleオブジェクト(より正確には、coroutine_handle :: addressによって返されるアドレス)がコンテキストとして渡されます。



私が言ったように、コルーチンを破壊する責任はプログラマーにあります。これには破壊メソッドがあります。 原則として、コルーチンを操作するときにいつでも呼び出すことができますが、覚えておくべき主なことは、私たちがどのようなコンテキストで行っているかです。



実行コンテキスト



したがって、coroutine_handleは、あるコンテキストで現在の関数の実行を一時停止(フリーズ)し、別のコンテキストで実行を継続する機能を提供します。 実行はどこでも続行できます。別のスレッド、Wait *関数でのイベントの非同期処理などです。 純粋に理論的には、別のプロセスまたは中断でさえありえます。 同時に、関数のすべてのローカル変数とパラメーターは通常スタック上ではなく、ヒープ上にあります(new演算子とdelete演算子を覚えておいてください)。ただし、現在の提案では、コンパイラはヒープからメモリを割り当てないように最適化できます。



前の段落で、私たちが実行されるコンテキストを理解する必要があると述べました。 destroyメソッドはコルーチンの実行をキャンセルしますが、destroyメソッドの呼び出しがプログラムの安定性にどのように影響するかを理解することが重要です-別のスレッドまたは非同期操作が既に実行を開始する可能性があるため、これを覚えておくことが重要です。 さらに、システムプログラミングでは、多くの場合、一部の非同期操作はリソースを管理する機能へのアクセスを禁止します(たとえば、EFIでは、高TPLでメモリを割り当てたり割り当てたりすることはできません)-これも重要なポイントであり、覚えておく必要があります。



概念実証



次の環境で概念実証用のコルチンを使用しています。





私はC ++ Siberia 2016で講演するので、この投稿はシードと考えることができます。そこで、コルーチンを使用することのすべてのニュアンスをより詳細に分析します。



希望、コメント、訂正はあらゆる方法で歓迎されます。会議で会いましょう!



ps前述のホルスのレポートをご覧になることをお勧めします。このレポートは本当にクールです!



All Articles