[解決済み] C++11のasync(launch::async)は、高価なスレッド生成を避けるためにスレッドプールを廃止するか?
質問
この質問と緩やかに関連しています。 C++11でstd::thread pooledはありますか? . 質問は異なりますが、意図は同じです。
質問1:高価なスレッド生成を避けるために、独自の(またはサードパーティの)スレッドプールを使用することはまだ意味があるのでしょうか?
もう1つの質問の結論は、「あなたは
std::thread
がプールされているかどうかは当てにならない(かもしれないし、そうでないかもしれない)、というのがもう一つの質問の結論でした。しかし
std::async(launch::async)
はプールされる確率がかなり高いようです。
標準によって強制されているとは思いませんが、IMHO では、すべての優れた 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
によって生成されたスレッドでは、スレッドが再利用された可能性があるため、 私にとってはやや不明です。私の理解では、スレッド ローカル変数がリセットされることは保証されませんが、私が間違っているかもしれません。 - 一方、独自の (固定サイズの) スレッド プールを使用すると、本当に必要な場合は完全に制御することができます。
どのように解決するのですか?
質問1 :
原文がおかしいので、原文から変更しました。私は、次のような印象を持ちました。 Linux のスレッド作成は非常に安かった という印象を持っていたのですが、テストの結果、新しいスレッドでの関数呼び出しと通常のスレッドのオーバーヘッドが莫大であることを突き止めたのです。関数呼び出しを処理するためにスレッドを作成するオーバーヘッドは、普通の関数呼び出しに比べて 10000 倍以上遅いというようなものです。したがって、小さな関数呼び出しを大量に発行する場合は、スレッド プールが良いアイデアかもしれません。
g++ に同梱されている標準 C++ ライブラリには、スレッドプールがないことは明らかです。しかし、私は間違いなくスレッドプールのケースを見ることができます。ある種のスレッド間キューに呼び出しを押し込まなければならないというオーバーヘッドがあっても、おそらく新しいスレッドを立ち上げるより安上がりでしょう。そして、標準はこれを許可しています。
IMHO では、Linux カーネルの人々は、スレッド生成を現在よりも安くすることに取り組むべきです。しかし、標準 C++ ライブラリでは、プールを使って
launch::async | launch::deferred
.
そして、OPが正しいのは
::std::thread
を使ってスレッドを起動すると、当然ながらプールからのスレッドを使うのではなく、新しいスレッドを作らざるを得ません。そのため
::std::async(::std::launch::async, ...)
が望ましいです。
質問2 :
はい、基本的にこれは「暗黙のうちに」スレッドを立ち上げています。しかし、本当に、何が起こっているかはまだ非常に明白です。だから、暗黙のうちにという言葉は、特に良い言葉だとは思いません。
私はまた、破壊の前にリターンを待つことを強制することが必ずしもエラーであるとは納得していません。というのも、私はあなたが
async
の呼び出しで、戻ることが期待されていない 'デーモン' スレッドを作成することはできません。そして、それらが戻ることを期待されている場合、例外を無視することはOKではありません。
質問3 :
個人的には、スレッドの起動は明示的であることが好きです。私は、シリアルアクセスを保証できる島々に多くの価値を置いています。そうでなければ、常にどこかでmutexをラップし、それを使用することを忘れないようにしなければならないミュータブルステートで終わってしまいます。
私は「未来」モデルよりもワークキューモデルの方がずっと好きでした。なぜなら、「シリアルの島」が転がっているので、より効果的にミュータブルステートを扱うことができるからです。
しかし、実際には、あなたが何をしているかによります。
パフォーマンス テスト
そこで、さまざまな呼び出し方法のパフォーマンスをテストし、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
そしてネイティブでは、私の MacBook Pro 15" (Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz) に
Apple LLVM version 10.0.0 (clang-1000.10.44.4)
を OSX 10.13.6 で使用した場合、次のようになります。
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
ワーカスレッドでは、スレッドを立ち上げ、ロックレスキューを使って別のスレッドにリクエストを送り、quot;完了しました"の返信が返ってくるのを待ちました。
何もしない"は、テストハーネスのオーバーヘッドをテストするためだけです。
スレッドを起動するためのオーバーヘッドが膨大であることは明らかです。そして、スレッド間キューを持つワーカスレッドでさえ、VM の Fedora 25 では 20 倍ほど、ネイティブ OS X では約 8 倍の速度が低下しています。
私は、パフォーマンス テストに使用したコードを保持する OSDN チャンバーを作成しました。それはここで見ることができます。 https://osdn.net/users/omnifarious/pf/launch_thread_performance/
関連
-
[解決済み] string does not name a type Errorが発生するのはなぜですか?
-
[解決済み】'cout'は型名ではない
-
[解決済み] 既に.objで定義されている-二重包含はない
-
[解決済み】「Expected '(' for function-style cast or type construction」エラーの意味とは?
-
[解決済み】浮動小数点数の乱数生成
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み] C++テンプレート関数定義の.CPPファイルへの格納
-
[解決済み] どちらが速いですか?スタックアロケーションとヒープアロケーション
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】非同期プログラミングとマルチスレッドの違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++ クラスヘッダが含まれているときに「不明な型」があるのはなぜですか?重複
-
[解決済み】C++ - 解放されるポインタが割り当てられていないエラー
-
[解決済み】テンプレートの引数1が無効です(Code::Blocks Win Vista) - テンプレートは使いません。
-
[解決済み】cc1plus:エラー:g++で認識されないコマンドラインオプション"-std=c++11"
-
[解決済み】オブジェクト引数のない非静的メンバ関数の呼び出し コンパイラーエラー
-
[解決済み】クラステンプレートの使用にはテンプレート引数リストが必要です
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み】std::cin.getline( ) vs. std::cin
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)