132

この質問と大まかに関連しています: Are std::thread pooled in C++11? . 質問は異なりますが、意図は同じです。

質問 1: 高価なスレッドの作成を避けるために、独自の (またはサードパーティのライブラリ) スレッド プールを使用することは依然として理にかなっていますか?

他の質問の結論は、プールされることを当てstd::threadにできないということでした (そうかもしれないし、そうでないかもしれません)。ただし、std::async(launch::async)プールされる可能性ははるかに高いようです。

標準で強制されているとは思いませんが、スレッドの作成が遅い場合、すべての優れた C++11 実装でスレッド プーリングが使用されると思います。新しいスレッドを作成するのに費用がかからないプラットフォームでのみ、常に新しいスレッドを生成することが期待されます。

質問 2: これは私の考えですが、それを証明する事実はありません。私は間違っているかもしれません。勝手な推測ですか?

最後に、スレッドの作成を次のように表現できると思う方法を最初に示すサンプルコードをいくつか提供しましたasync(launch::async)

例 1:

 thread t([]{ f(); });
 // ...
 t.join();

になる

 auto future = async(launch::async, []{ f(); });
 // ...
 future.wait();

例 2: スレッドを起動して忘れる

 thread([]{ f(); }).detach();

になる

 // a bit clumsy...
 auto dummy = async(launch::async, []{ f(); });

 // ... but I hope soon it can be simplified to
 async(launch::async, []{ f(); });

質問 3:バージョンよりもasyncバージョンの方が好みthreadですか?


残りはもはや質問の一部ではありませんが、明確にするためだけです:

戻り値をダミー変数に代入する必要があるのはなぜですか?

残念ながら、現在の C++11 標準では、 の戻り値をキャプチャする必要がありますstd::async。そうしないと、デストラクタが実行され、アクションが終了するまでブロックされます。これは、標準のエラーと見なされる人もいます (たとえば、Herb Sutter による)。

cppreference.com のこの例は、それをうまく示しています。

{
  std::async(std::launch::async, []{ f(); });
  std::async(std::launch::async, []{ g(); });  // does not run until f() completes
}

別の説明:

スレッドプールには他の正当な用途があることは知っていますが、この質問では、高価なスレッド作成コストを回避するという側面にのみ関心があります

特にリソースをより細かく制御する必要がある場合など、スレッド プールが非常に役立つ状況がまだあると思います。たとえば、サーバーは、高速な応答時間を保証し、メモリ使用量の予測可能性を高めるために、一定数の要求のみを同時に処理することを決定する場合があります。ここでは、スレッドプールは問題ないはずです。

スレッド ローカル変数は、独自のスレッド プールの引数にもなる可能性がありますが、実際に関連するかどうかはわかりません。

  • std::threadスレッドローカル変数を初期化せずに開始する新しいスレッドを作成します。多分これはあなたが望むものではありません。
  • によって生成されasyncたスレッドでは、スレッドが再利用された可能性があるため、私にはやや不明です。私の理解では、スレッド ローカル変数がリセットされる保証はありませんが、間違っている可能性があります。
  • 一方、独自の (固定サイズの) スレッド プールを使用すると、本当に必要な場合に完全に制御できます。
4

1 に答える 1

70

質問 1 :

オリジナルが間違っていたので、これをオリジナルから変更しました。Linux スレッドの作成は非常に安価であるという印象を受けました。テストの結果、新しいスレッドでの関数呼び出しと通常のスレッドでの関数呼び出しのオーバーヘッドが非常に大きいことがわかりました。関数呼び出しを処理するスレッドを作成するためのオーバーヘッドは、単純な関数呼び出しよりも 10000 倍以上遅くなります。そのため、小規模な関数呼び出しを多数発行している場合は、スレッド プールを使用することをお勧めします。

g++ に同梱されている標準 C++ ライブラリにスレッド プールがないことは明らかです。しかし、私は間違いなく彼らのケースを見ることができます. 何らかの種類のスレッド間キューを介して呼び出しを押し込む必要があるというオーバーヘッドがあっても、新しいスレッドを開始するよりも安価になる可能性があります。そして、標準ではこれが許可されています。

私見ですが、Linux カーネルの人々は、スレッドの作成を現在よりも安くすることに取り組むべきです。ただし、標準 C++ ライブラリでは、プールを使用して を実装することも検討する必要がありますlaunch::async | launch::deferred

OPは正しいです。::std::threadもちろん、スレッドを起動するために使用すると、プールからスレッドを使用する代わりに、新しいスレッドが強制的に作成されます。だから::std::async(::std::launch::async, ...)好ましいです。

質問 2 :

はい、基本的にこれは「暗黙的に」スレッドを起動します。しかし、実際には、何が起こっているのかはまだ明らかです。ですから、暗黙のうちにという言葉が特に良い言葉だとは思いません。

また、破壊する前に復帰を待つことを強制することが必ずしも誤りであるとは確信していません。async返されることが期待されていない「デーモン」スレッドを作成するために呼び出しを使用する必要があるかどうかはわかりません。そして、それらが返されることが期待されている場合、例外を無視することは問題です。

質問 3 :

個人的には、スレッドの起動が明示的であることを好みます。シリアルアクセスが保証できる島を大切にしています。そうしないと、常にミューテックスをどこかにラップして、それを使用することを覚えておく必要があるという可変状態になります。

ワーク キュー モデルは「未来」モデルよりもはるかに優れていました。「シリアルの島」が横たわっているため、変更可能な状態をより効果的に処理できるからです。

しかし、実際には、それはあなたが何をしているかによって異なります。

性能テスト

そこで、さまざまな呼び出し方法のパフォーマンスをテストし、clang バージョン 7.0.1 および libc++ (libstdc++ ではない) でコンパイルされた Fedora 29 を実行する 8 コア (AMD Ryzen 7 2700X) システムでこれらの数値を見つけました。

   Do nothing calls per second:   35365257                                      
        Empty calls per second:   35210682                                      
   New thread calls per second:      62356                                      
 Async launch calls per second:      68869                                      
Worker thread calls per second:     970415                                      

OSX 10.13.6を搭載した私の MacBook Pro 15 インチ (Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz)Apple LLVM version 10.0.0 (clang-1000.10.44.4)では、次のようになります。

   Do nothing calls per second:   22078079
        Empty calls per second:   21847547
   New thread calls per second:      43326
 Async launch calls per second:      58684
Worker thread calls per second:    2053775

ワーカー スレッドについては、スレッドを開始し、ロックレス キューを使用して別のスレッドに要求を送信し、「完了しました」という応答が返されるのを待ちました。

「何もしない」は、テスト ハーネスのオーバーヘッドをテストするだけです。

スレッドを起動するオーバーヘッドが膨大であることは明らかです。また、スレッド間キューを持つワーカー スレッドでさえ、VM の Fedora 25 では約 20 倍、ネイティブ OS X では約 8 倍遅くなります。

パフォーマンス テストに使用したコードを保持する OSDN チャンバーを作成しました。ここにあります: https://osdn.net/users/omnifarious/pf/launch_thread_performance/

于 2013-01-16T04:37:24.960 に答える