when_any
ゼロ先物が渡されたときに選択する4つの設計オプションがありますが、残念ながらそれらはすべて理にかなっています。
今まで私はできる
when_any(zero future<T>s)
永遠にブロックする未来を返す必要があります。最初の理由は、定義の単純さと均一性のためです。
when_any(zero futures)が永久にブロックする未来を返す場合、次のようになります。
wait_allは、一部が未完了の間、すべてが完了するまでブロックします。
wait_anyは、すべてが未完了の場合ではなく、一部が終了した場合にブロックを解除します。
when_any(zero futures)がすぐに準備ができている未来を返す場合、次のようになります。
wait_allは、一部が未完了の間、すべてが完了するまでブロックします。
wait_anyは、すべてが未完了の場合ではなく、一部が終了した場合にブロックを解除しますが、先物がゼロの場合はブロックを解除します。
2番目の理由はwhen_any
、結合法則と可換二項演算であるため、可変個引数バージョンwhen_any
の場合、when_any
演算の単位元(永久にブロックされる未来)を返したいということです。また、独自の二項演算子を定義できる言語(C ++が将来それを行う可能性があります)またはstd::accumulate
アルゴリズムがサポートされている言語では、遅かれ早かれこの単位元の問題が発生します。
when_all
はのようなものoperator&&
であり、パラメータパックの展開では、空のパックがtrue
foroperator&&
に展開され、true
すぐに準備ができている未来のようなものです。
when_any
のようなものoperator||
であり、パラメータパックの展開では、空のパックがfalse
foroperator||
に展開され、false
準備ができていない未来のようなものです。
(ID要素が必要な他の場所は次のとおりです。
boost::thread::null_mutex
to std::scoped_lock
(std::scoped_lock
より小さなロックを消費し、より大きなロックを生成する結合法則と可換二項演算のようなものです)、std::monostate
to std::variant
(std::variant
より小さな和集合を消費し、より大きな和集合を生成する結合法則と可換二項演算のようなものです)、operator|
regexpで空に設定されoperator|
ます(NFAをregexpに変換するプログラムを作成すると、子がゼロのノードが発生する可能性があります)、operator concat
正規表現の空の文字列、)
発散をどのように扱うべきですか?
プログラムは次のようになります。
発散は値ではなく、発散はエラーではなく、発散は発散です。
オペレーティングシステム、プロトコルスタック、データベースサービス、Webサーバーなど、分岐する(終了しない)プログラムがあります。
発散に対処する方法があります。C#には、キャンセル機能と進捗レポート機能があります。C ++では、実行エージェント(boost.thread)を中断したり、実行エージェント(boost.context、boost.fiber)を破棄したりできます。スレッドセーフなキューまたはチャネルを使用して、アクターとの間で値/エラーを継続的に送受信できます。
発散のユースケース①:
プログラマーはlibrary1を使用して、信頼性の低いネットワーク上の信頼性の低いWebサービスを調べます。ネットワークの信頼性が低いため、library1は永久に再試行します。次の理由により、タイムアウトが期限切れになったときに、 library1自体が例外を共有状態で格納するべきではありません。
とにかく、プログラマーはwhen_any
、潜在的にブロックする永遠の未来を彼自身のmy-cancelation / fallback-machanismの未来とマージして、より大きな未来を手に入れる必要があります。
(when_any(several future<T>...)
リターンfuture<T>
を想定しているので、フューチャーツリーのすべての中間のwhen_anyノードで定型コードを記述する必要はありません。)
(いくつかの変更が必要です。(1)when_any
リターンが大きいフューチャーは、最初の子フューチャーの準備ができたときに他の子フューチャーを破棄する必要があります。 (2)library1は、promiseオブジェクトを使用しif(shared_state.reference_count == 1)
て、コンシューマーが将来を放棄した(つまり、操作がキャンセルされた)ことを確認して認識し、そのループを終了する必要があります;)
発散のユースケース②:
プログラマーはlibrary2を使用して、信頼性の低いネットワーク上の信頼性の低いWebサービスを調べます。library2はn回再試行してから、物理的にではなく論理的に、shared_state(shared_state.diverge = true
またはstared_state.state = state_t::diverge
)にビットを設定することにより、永久にブロックします。プログラマーはwhen_any
、library2からの未来とmy-cancelation / fallback-machanismfutureをマージするために使用します。最初の準備ができた子の未来は結果を示します。失敗した子の未来が永久にブロックされるのではなく、例外を除いて準備ができたとすると、後で準備ができた成功した子の未来の代わりに、より大きな未来に答えます。これは望ましくありません。
(when_any(several future<T>...)
リターンfuture<T>
を想定しているので、将来のツリーのすべての中間のwhen_anyノードで定型コードを記述する必要はありません。)
発散のユースケース③:
ネットワークコードをテストするときは、ネットワーク状態が非常に悪いクライアントを表す準備ができていないフューチャーを使用し、ネットワーク状態が非常に良いクライアントをすぐに表すことができるフューチャーを使用し、さまざまなタイムアウトのあるフューチャーを使用してスタンドします。間にあるクライアント向け。
(いくつかの変更が必要です:(1)make_diverging_futureを追加します;(2)make_timeout_ready_futureを追加します;)
when_any(zero future<T>s)
例外を含むfutureを返す必要があります。c ++-std :: when_any()の非ポーリング実装-コードレビュースタック交換https://codereview.stackexchange.com/questions/176168/non-polling-implementation-of-stdwhen-any
並行性TS
when_any
は、哲学的に-引数なしで呼び出されたときに、準備ができた未来を誤って返します。私のバージョンはそのケースを特別に扱っていないので、自然な振る舞いが失われますpromise
。提供された0のいずれかの先物が準備できる前に内部が破壊されwhen_any(/*zero args*/)
、get()
がスローされる準備ができた先物が返されますbroken_promise
。
これは「早く失敗、大声で失敗」の場合だと思います。彼は発散をエラーとして扱っているため、上記のユースケースでは問題が発生します。
when_any(zero future<T>s)
???の値を含むfutureを返す必要があります。when_any(zero future<T>s)
禁止する必要があります。最後の3つの設計オプションは、標準とライブラリによって使用されます。以下で彼らの動機を推測してみます。
* _allおよび* _anyでのいくつかの実装とその動作は次のとおり
です。CPUバウンドプログラムの関数:(テーブルの読み取りに問題がある場合は、編集モードに移動します)
どこ | 関数 | ゼロタスクを通過したときの動作 |
---|---|---|
boost.thread * _all | void wait_for_all(...) |
戻る void |
boost.thread * _any | iterator wait_for_any(...) |
終了イテレータを返す |
boost.fiber * _all | void wait_all_simple(...) |
コンパイル時に拒否されました |
vector<R> wait_all_values(...) |
コンパイル時に拒否されました | |
vector<R> wait_all_until_error(...) |
コンパイル時に拒否されました | |
vector<R> wait_all_collect_errors(...) |
コンパイル時に拒否されました | |
R wait_all_members(...) |
の値を返します R |
|
boost.fiber * _any | void wait_first_simple(...) |
戻る void |
R wait_first_value(...) |
コンパイル時に拒否されました | |
R wait_first_outcome(...) |
コンパイル時に拒否されました | |
R wait_first_success(...) |
コンパイル時に拒否されました | |
variant<R> wait_first_value_het(...) |
コンパイル時に拒否されました | |
System.Threading.Tasks * _all | void Task.WaitAll(...) |
戻る void |
System.Threading.Tasks * _any | int Task.WaitAny(...) |
戻る -1 |
IOバウンドプログラムの関数:
どこ | 関数 | ゼロタスクを通過したときの動作 |
---|---|---|
std :: Experimental * _all | future<sequence<future<T>>> when_all(...) |
空のシーケンスを格納する将来を返します |
std :: Experimental * _any | future<...> when_any(...) |
将来の保管を返す { size index = -1, sequence<future<T>> sequence = empty sequence } |
boost.thread * _all | future<sequence<future<T>>> when_all(...) |
空のシーケンスを格納する将来を返します |
boost.thread * _any | future<sequence<future<T>>> when_any(...) |
空のシーケンスを格納する将来を返します |
System.Threading.Tasks * _all | Task<TResult[]> Task.WhenAll(...) |
空のシーケンスを格納する将来を返します |
System.Threading.Tasks * _any | Task<Task<TResult>> Task.WhenAny(...) |
実行時に拒否されました(throw ArgumentException ) |
(System.Threading.Tasks.WaitAny(...)
ゼロ先物を受け入れますがSystem.Threading.Tasks.WhenAny(...)
、実行時に拒否します。)
when_any(zero tasks)
永遠にブロックする未来を返してはいけない理由は、おそらく実用性です。それを許可すると、futureのインターフェースに穴が開いて、すべての未来が潜在的に分岐するため、すべてのアプリケーション層プログラマーはwhen_any
、未来をmy-cancelation / fallback-machanism futureとマージして、ブロックされないより大きな未来を取得する必要があります。面倒な詳細情報が不足しています。それを許可しない場合は、すべてのインターフェイスが詳細に文書化されていないチームを保護します(例えを挙げましょう:ライブラリ関数がandのnullptr
代わりに潜在的なポインタを受け取り、返すC ++の会社にいると想像してください。詳細や文書はoptional<reference_wrapper<T>>
ありませんが、reference_wrapper<T>
すべてのメンバーのアクセス式を次のように保護する必要がありますif(p)
、退屈です。先物と同様に、私たちはwhen_any(future_returned_from_library, std::make_timeout_future(1s))
どこでもしなければなりません)。したがって、インターフェースと可能性をできるだけ小さくする方がよいでしょう。
(Alan Birtlesに謝罪します:リストされた実装についてその日に間違えたのは残念です:最初の関数以外のboost.fiberのwait_any関数はゼロフューチャーを禁止し、broken_promiseを格納するフューチャーを返す個別の実装があります(https: //codereview.stackexchange.com/questions/176168/non-polling-implementation-of-stdwhen-any)なので、新しい質問にまとめようとしています。)
私は標準的な解決策を選びます:https://en.cppreference.com/w/cpp/experimental/when_any
- 範囲が空の場合(つまり、first == last)、返されるfutureはすぐに準備ができています。when_any_resultのfuturesフィールドは空のベクトルであり、indexフィールドはsize_t(-1)です。
- 引数が指定されていない場合、返されるfutureはすぐに準備ができています。when_any_resultのfuturesフィールドは空のタプルであり、indexフィールドはsize_t(-1)です。
また、他の「潜在的に望ましい動作」のほとんどは、受信したリストに「空の」シードフューチャーを追加するだけで簡単に構成できることにも注意してください。
auto my_when_any = [](auto... f) {
return when_any(EmptyT{}, f...);
};
ここではEmptyT
、常に準備ができている将来の可能性があり、準備ができてことはありませんか、あなたの好みに応じて例外を保持しています。
これは、例えば倍の表現に非常によく似ていますがmonoidal「シード」を決める:(false || ... || pack)
対(true && ... && pack)
あなたが参照されます。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加