私が知っているように、async
は別のスレッド/プロセス/コアで関数を実行し、メインスレッドをブロックしませんが、常にそうですか?
私は次のコードを持っています:
async(launch::async,[]()
{
Sleep(1000);
puts("async");
});
puts("main");
async main
、つまりメインスレッドはasync
が終了するまで待機するということですか?
次のように変更した場合:
auto f = async(launch::async,[]() // add "auto f = "
{
Sleep(1000);
puts("async");
});
puts("main");
main async
。これにより、mainはasync
が完了するのを待機していないように見えます。
私が知っているように、非同期は別のスレッド/プロセス/コアで関数を実行し、メインスレッドをブロックしませんが、それは常に発生しますか?
_std::async
__std::launch::async
_ が最初の引数として渡される場合のみ、別のスレッドで実行されることが保証されます。
- _
std::launch::async
_:新しいスレッドが起動され、タスクを非同期に実行します- _
std::launch::deferred
_タスクが最初に結果が要求されたときに呼び出しスレッドで実行されます(遅延評価)
デフォルトの起動ポリシーは_std::launch::async | std::launch::deferred
_です。
_std::async
_は _std::future
_ を返します。 _std::future
_のデストラクタ は、未来が_std::async
_から返された場合にのみブロックされます。
これらのアクションは、共有状態が準備完了になるのをブロックしませんが、次のすべてが当てはまる場合はブロックすることがあります。共有状態はstd :: asyncの呼び出しによって作成され、共有状態はまだ準備ができていません共有状態への最後の参照でした
最初のコードスニペットで、rvalue式を作成します。これはすぐに破棄されます-したがって_"async"
_は_"main"
_の前に出力されます。
非同期の匿名関数が作成され、実行が開始されます。
非同期の匿名関数は破棄されます。
main
実行は、関数が完了するまでブロックされます。
_"async"
_が出力されます。
main
execuctionが再開します。
"main"
_が出力されます。2番目のコードスニペットでは、ライフタイムが変数f
にバインドされているlvalue式を作成します。 f
は、main
関数のスコープの最後で破棄されます-したがって、_"main"
_が出力されますDelay(1000)
により_"async"
_の前。
非同期の匿名関数が作成され、実行が開始されます。
"async"
_がすぐに印刷されるのを遅らせるDelay(1000)
があります。main
実行が続行されます。
"main"
_が出力されます。main
のスコープの終わり。
非同期の匿名関数は破棄されます。
main
実行は、関数が完了するまでブロックされます。
_"async"
_が出力されます。
async main
、つまりメインスレッドはasync
が終了するまで待機するということですか?
はい、そうですが、それはasync
から返されたフューチャーをキャプチャしないためです。 async
は、スレッドが完了するまで、デストラクタでブロックされて返されるfuture
が特別です。返されたfuture
をキャプチャしないので
async(launch::async,[]()
{
Sleep(1000);
puts("async");
});
返されたfuture
は式の最後で破棄されるため、現在のスレッドで進行する前に終了する必要があります。
main async
。これにより、mainはasync
が完了するのを待機していないように見えます。
これは、async
を呼び出すときに本当に必要なものです。未来を捉えたので、非同期タスクが完了するまでメインスレッドを続行できます。そのスレッドに遅延があるため、main
はスレッドの前に出力されます。
std::launch::async
を渡した場合、std::async
は、独自のスレッドで実行されているかのようにタスクを実行する必要があります。
C++でのスレッド化の唯一の概念はstd::thread
です。
std::async
は、一意のプロパティを持つstd::future
を返します。破棄された場合、std::async
に保存されているタスクの完了時にブロックされます。これは、戻り値のキャプチャに失敗したときにトラップします。返されるstd::future
は、名前のない一時的なもので、「その行の終わり」で破棄されます。
この破棄は、async
タスクが完了するのを待ちます。
格納する場合、この遅延は、変数f
が破棄されるまで待機します。これは、出力後のmain
の最後にあります。
C++ 11の少なくとも1つの主要な実装であるMSVC 2015および2017には、新しいスレッドの代わりにスレッドプールを使用する、せいぜいわずかに準拠したstd::async
があることに注意してください。このスレッドプールは、実行時間の長いasync
呼び出しのセットが、他のasync
呼び出しの実行を停止する可能性があることを意味します。
スレッドプールの使用は合法です(スレッドローカルを再作成する限り)。ただし、既存のスレッドがすべて「長すぎる」ためにビジー状態になっている場合は、リソース不足を回避し、新しいスレッドを作成する必要があります。
規格は、スレッドが前進する必要があることを「推奨」するだけであるため、わずかに準拠しています。 C++では、ランダムな理由で進行しないスレッドは合法です。そして、ある意味では、それがstd::async
がそれらのケースでエミュレートするものであり、as-ifテストに合格することを主張できます。
これは、_std::future
_のdestructor(_std::async
_から返される)がタスクの完了を待機するためです。
最初のコードスニペットでは、_std::future
_から返された一時的な_std::async
_オブジェクトがステートメントの最後で破棄されます。これは https://en.cppreference.com/w/cpp/language/lifetime
すべての一時オブジェクトは、それらが(字句的に)作成されたポイントを含む完全式を評価する最後のステップとして破棄されます
したがって、次のステートメントを実行する前に、_std::future
_オブジェクトのデストラクタは、タスクが完了するまでブロックします。つまり、puts("async")
がputs("main")
の前に実行されます。
ただし、2番目のコードスニペットでは、std :: asyncの戻り値はmovedでローカルオブジェクトになり、スコープを終了すると破棄されます。したがって、async
を含む行はブロックなしで実行され、puts("main")
はputs("async")
(Sleep
呼び出しによってブロックされます)の前に実行されます。 。
この動作は https://en.cppreference.com/w/cpp/thread/async で次のように説明されています。
Std :: asyncから取得したstd :: futureが参照から移動または参照にバインドされていない場合、std :: futureのデストラクタは、非同期操作が完了するまで完全な式の最後でブロックし、基本的に次のようなコードを作成します。次の同期:
_std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes
_
本のItem 38Effective Modern C++ では、これは次のように表されます。
Std :: asyncを介して起動された非遅延タスクの共有状態を参照する最後の未来のデストラクタは、タスクが完了するまでブロックします。本質的に、そのようなフューチャのデストラクタは、非同期実行タスクが実行されているスレッドで暗黙的な結合を行います。