web-dev-qa-db-ja.com

非同期は常にC ++で別のスレッド/コア/プロセスを使用しますか?

私が知っているように、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が完了するのを待機していないように見えます。

26
Person.Junkie

私が知っているように、非同期は別のスレッド/プロセス/コアで関数を実行し、メインスレッドをブロックしませんが、それは常に発生しますか?

_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"_の前に出力されます。

    1. 非同期の匿名関数が作成され、実行が開始されます。

    2. 非同期の匿名関数は破棄されます。

      • main実行は、関数が完了するまでブロックされます。

      • _"async"_が出力されます。

    3. main execuctionが再開します。

      • _"main"_が出力されます。

  • 2番目のコードスニペットでは、ライフタイムが変数fにバインドされているlvalue式を作成します。 fは、main関数のスコープの最後で破棄されます-したがって、_"main"_が出力されますDelay(1000)により_"async"_の前。

    1. 非同期の匿名関数が作成され、実行が開始されます。

      • _"async"_がすぐに印刷されるのを遅らせるDelay(1000)があります。
    2. main実行が続行されます。

      • _"main"_が出力されます。
    3. mainのスコープの終わり。

    4. 非同期の匿名関数は破棄されます。

      • main実行は、関数が完了するまでブロックされます。

      • _"async"_が出力されます。

27
Vittorio Romeo

async main、つまりメインスレッドはasyncが終了するまで待機するということですか?

はい、そうですが、それはasyncから返されたフューチャーをキャプチャしないためです。 asyncは、スレッドが完了するまで、デストラクタでブロックされて返されるfutureが特別です。返されたfutureをキャプチャしないので

async(launch::async,[]()
{
    Sleep(1000);
    puts("async");
});

返されたfutureは式の最後で破棄されるため、現在のスレッドで進行する前に終了する必要があります。

main async。これにより、mainはasyncが完了するのを待機していないように見えます。

これは、asyncを呼び出すときに本当に必要なものです。未来を捉えたので、非同期タスクが完了するまでメインスレッドを続行できます。そのスレッドに遅延があるため、mainはスレッドの前に出力されます。

6
NathanOliver

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を介して起動された非遅延タスクの共有状態を参照する最後の未来のデストラクタは、タスクが完了するまでブロックします。本質的に、そのようなフューチャのデストラクタは、非同期実行タスクが実行されているスレッドで暗黙的な結合を行います。

0
Ozgur Murat