「前に強く起こる」とはどういう意味ですか?


9

C ++ドラフト標準では、「前に強く起こる」というフレーズが何度か使用されています。

例:終了 [basic.start.term] / 5

std :: atexitへの呼び出し([support.start.term]を参照)の前に、静的ストレージ期間を持つオブジェクトの初期化が強く発生した場合、std :: atexitに渡された関数の呼び出しオブジェクトのデストラクタを呼び出す前にシーケンス化されます。std :: atexitの呼び出しが静的ストレージ期間のオブジェクトの初期化が完了する前に強く発生する場合、オブジェクトのデストラクタの呼び出しは、std :: atexitに渡される関数の呼び出しの前にシーケンスされます。std :: atexitへの呼び出しがstd :: atexitへの別の呼び出しの前に強く発生する場合、2番目のstd :: atexit呼び出しに渡される関数の呼び出しは、関数に渡される前にシーケンスされます最初のstd :: atexit呼び出し。

データレース [intro.races] / 12で定義

評価Aは、評価Dの前に強く発生します。

(12.1)AがDの前にシーケンスされる、または

(12.2)AがDと同期し、AとDの両方が順次一貫したアトミック操作([atomics.order])である、または

(12.3)評価BとCがあり、AはBの前にシーケンスされ、Bは単にCの前に発生し、CはDの前にシーケンスされる、または

(12.4)AがBの前に強く発生し、BがDの前に強く発生するという評価Bがあります。

[注:非公式に、AがBの前に強く発生する場合、AはすべてのコンテキストでBの前に評価されるように見えます。excludeが操作を消費する前に強く発生します。—エンドノート]

なぜ「以前に強く起こる」ことが導入されたのですか?直感的に、その違いと「以前の出来事」との関係は何ですか?

ノートの「AはすべてのコンテキストでBより先に評価されるように見える」とはどういう意味ですか?

(注:この質問の動機は、この回答の下でのPeter Cordesのコメントです。)

追加の標準草案(Peter Cordesに感謝)

順序と一貫性 [atomics.order] / 4

すべてのmemory_order :: seq_cst操作(フェンスを含む)には、次の制約を満たす単一の合計順序Sがあります。まず、AとBがmemory_order :: seq_cst操作で、AがBの前に強く発生する場合、AはSでBの前に発生します。 Bの前に、Sが満たすには、次の4つの条件が必要です。

(4.1)AとBが両方ともmemory_order :: seq_cst演算である場合、AはSでBに先行します。そして

(4.2)Aがmemory_order :: seq_cst演算であり、Bがmemory_order :: seq_cstフェンスYの前に発生する場合、AはSでYに先行します。そして

(4.3)memory_order :: seq_cstフェンスXがAの前に発生し、Bがmemory_order :: seq_cst演算である場合、SでXがBに先行します。そして

(4.4)memory_order :: seq_cstフェンスXがAの前に発生し、Bがmemory_order :: seq_cstフェンスYの前に発生する場合、XはSでYに先行します。


1
現在のドラフト規格は、参照のために適用するルールの条件として、「AはBを強く前に発生」seq_cstには、:アトミック31.4秩序と一貫性 4。これはC ++ 17 n4659標準にはありません。ここで、32.4-3は、影響を受けるすべての場所の「前に発生する」順序と変更順序と一致する seq_cst opsの単一の合計順序の存在を定義します。「強く」は後のドラフトで追加されました。
Peter Cordes

2
@PeterCordes消費を除いたコメントは、HBが「すべてのコンテキストで」/「強力」であり、関数ポインターの呼び出しについて話していることは、まったく意味のないことだと思います。マルチスレッドプログラムがatexit()1つのスレッドとexit()別のスレッドで呼び出す場合、結果exit()が同じスレッドで呼び出された場合とは異なるため、初期化子が消費ベースの依存関係のみを運ぶだけでは不十分です。私の古い回答はこの違いを懸念していました。
Iwillnotexist Idonotexist


@IwillnotexistIdonotexist MTプログラムを終了することもできますか?基本的に壊れたアイデアではありませんか?
curiousguy

1
@curiousguyそれがの目的ですexit()どのスレッドも終了することでプログラム全体をreturn強制終了できます。またはメインスレッドは-ingで終了できます。その結果、atexit()ハンドラーが呼び出され、すべてのスレッドが実行していたすべてのスレッドが停止します。
Iwillnotexist Idonotexist

回答:


5

なぜ「以前に強く起こる」ことが導入されたのですか?直感的に、その違いと「以前の出来事」との関係は何ですか?

「先に起きるだけ」にも気をつけて!このcpprefの現在のスナップショットを見て くださいhttps://en.cppreference.com/w/cpp/atomic/memory_order

ここに画像の説明を入力してください

C ++ 20で「単純に前に起こる」が追加されたようです。

単に前に起こる

スレッドに関係なく、次のいずれかに該当する場合、評価Aは評価Bの前に発生します。

1)AはBより前にシーケンスされます

2)AがBと同期

3)Aは単にXの前に発生し、Xは単にBの前に発生する

注:消費操作がない場合、単純に発生前と発生前の関係は同じです。

したがって、Simply-HBとHBは、消費操作の処理方法を除いて同じです。HBを参照

前に起こる

スレッドに関係なく、次のいずれかに該当する場合、評価Aは評価Bの前に行われます。

1)AはBより前にシーケンスされます

2)スレッド間がBの前に発生する

実装は、必要に応じて追加の同期を導入することにより、発生前の関係が非循環であることを保証するために必要です(消費操作が関係する場合にのみ必要になる可能性があります。Battyet alを参照)

消費に関してどのように違いますか?Inter-Thread-HBを参照してください

インタースレッドは前に発生します

スレッド間、評価次のいずれかに該当する場合、評価Bの前にスレッド間が発生します。

1)AがBと同期

2)AはBの前に依存関係順です

3)...

...

依存関係が順序付けられている(つまり、リリース/消費を使用する)操作はHBですが、必ずしも単にHBである必要はありません。

消費は獲得よりもリラックスしているので、私が正しく理解していれば、HBは単にシンプリーHBよりもリラックスしています。

強く発生する

スレッドに関係なく、次のいずれかに該当する場合、評価Aは評価Bの前に強く発生します。

1)AはBより前にシーケンスされます

2)AはBと同期し、AとBの両方が順次一貫したアトミック操作です

3)AはXの前にシーケンスされ、Xは単にYの前に発生し、YはBの前にシーケンスされる

4)Xの前に強く発生し、XはBの前に強く発生

注:非公式に、AがBの前に強く発生する場合、AはすべてのコンテキストでBの前に評価されるように見えます。

注:完全に発生する前に除外操作を消費します。

そのため、解放/消費操作をStrongly-HBにすることはできません。

リリース/取得はHBおよびSimply-HBにすることができます(リリース/取得は同期するため)。必ずしも厳密にHBである必要はありません。Strongly-HBは、AがBと同期しなければならず、かつSequentially Consistent操作である必要があると明確に述べているためです。

                            Is happens-before guaranteed?

                        HB             Simply-HB          Strongly-HB

relaxed                 no                 no                 no
release/consume        yes                 no                 no      
release/acquire        yes                yes                 no
S.C.                   yes                yes                yes

ノートの「AはすべてのコンテキストでBより先に評価されるように見える」とはどういう意味ですか?

すべてのコンテキスト:すべてのスレッド/すべてのCPUが同じ順序を参照します(または「最終的に同意する」)。これは、シーケンシャルな一貫性の保証です。つまり、すべての変数のグローバルな合計変更順序です。取得/解放チェーンは、チェーンに参加しているスレッドの認識された変更順序のみを保証します。チェーンの外のスレッドは、理論的には別の順序を見ることができます。

なぜStrongly-HBとSimply-HBが導入されたのか分かりません。多分消費の周りで動作する方法を明確にするのを助けるために?強くHBには優れた特性があります-1つのスレッドがBの前に強く発生するBを観察する場合、すべてのスレッドが同じことを観察することがわかります。

消費の歴史:

Paul E. McKenneyは、CおよびC ++標準に準拠する責任があります。消費は、ポインタ割り当てとそれが指すメモリ間の順序付けを保証します。DEC Alphaのために発明されました。DEC Alphaは投機的にポインターを逆参照する可能性があるため、これを防ぐためのメモリフェンスもありました。DEC Alphaはもう製造されておらず、現在、このような動作をするプロセッサはありません。消費は非常にリラックスすることを目的としています。


1
やれやれだぜ。私はその質問をすることをほとんど後悔している。私は戻って取り組むに行きたい簡単イテレータの無効化のルールのようなC ++の問題、引数依存名のルックアップ、テンプレートのユーザー定義の変換演算子、テンプレート引数控除、テンプレートのメンバーで、基本クラスで名前のルックアップルックス、そしてあなたをオブジェクトの構築の最初に仮想ベースに変換できます。
curiousguy

Re:消費します。消費順序の運命はDEC Alphaの運命に関連していて、その特定のアーチ以外では価値がないと主張しますか?
curiousguy

1
それは良い質問です。さらに詳しく見ると、消費は理論的にはARMやPowerPCのような弱く並べられたアーチのパフォーマンスを向上させる可能性があるようです。もう少し時間をかけて調べてください。
ハンフリーウィネベーゴ

1
消費が存在するのは、アルファ以外のすべての弱く順序付けられたISAが原因だと思います。Alpha asmでは、依存関係の順序ではなく、リラックスして取得(およびseq-cst)する唯一のオプションがあります。mo_consume実際のCPUでのデータ依存関係の順序付けを利用し、コンパイラーが分岐予測を介してデータ依存関係を壊すことができないことを形式化することを目的としています。たとえば、特定のポインタ値が共通であることを期待する何らかの理由がある場合int *p = load(); tmp = *p;、コンパイラが導入if(p==known_address) tmp = *known_address; else tmp=*p;することによって壊れる可能性があります。リラックスしていても消費することはできません。
Peter Cordes

@PeterCordes正しい...順序付けが弱いアーチは、取得のためにメモリバリアを放出する必要がありますが、(理論的には)消費するためではありません。アルファが存在しなかったとしても、私たちはまだ消費していると思いますか?また、あなたは基本的に、消費はファンシー(または「標準」)コンパイラの障壁であると言っています。
ハンフリーウィネベーゴ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.