それは本当に再入可能ではありません。同じスレッド(または異なるスレッド)で関数を2回実行していません。これは、再帰によって取得することも、現在の関数のアドレスをコールバック関数ポインタの引数として別の関数に渡すことで取得できます。(そしてそれは同期するのでそれは危険ではありません)。
これは、シグナルハンドラーとメインスレッドの間の単純なデータレースUB(Undefined Behaviour)にすぎませsig_atomic_t
ん。これに対して安全であることが保証されているだけです。8バイトのオブジェクトをx86-64の1つの命令でロードまたは保存でき、コンパイラーがたまたまそのasmを選択する場合のように、他のものが動作する場合があります。(@icarusの答えが示すように)。
MCUプログラミング-C ++ O2最適化がwhile whileループを参照してください-シングルコアマイクロコントローラーの割り込みハンドラーは、基本的にシングルスレッドプログラムのシグナルハンドラーと同じです。その場合、UBの結果として、ループから負荷が引き上げられます。
データレースUBが原因で実際にティアリングのテストケースが発生したのは、おそらく32ビットモードで開発またはテストされたか、構造体メンバーを個別にロードした古いdumberコンパイラを使用したためです。
あなたの場合、UBフリープログラムがストアを監視できないため、コンパイラはストアを無限ループから最適化できます。 data
is _Atomic
またはvolatile
でなく、ループ内に他の副作用はありません。したがって、リーダーがこのライターと同期する方法はありません。これは、最適化を有効にしてコンパイルした場合に実際に起こります(Godboltはメインの下部に空のループを表示します)。構造体も2に変更し、long long
gccはmovdqa
ループの前に単一の16バイトストアを使用します。(これはアトミックな保証はありませんが、整列されていると仮定すると、ほとんどすべてのCPUで実際に行われます。またはIntelではキャッシュラインの境界を越えないだけです 。x86で自然に整列された変数アトミックに整数が割り当てられるのはなぜですか?)
したがって、最適化を有効にしてコンパイルすると、テストが失敗し、毎回同じ値が表示されます。Cは移植可能なアセンブリ言語ではありません。
volatile struct two_int
また、コンパイラーにそれらを最適化しないよう強制しますが、構造体全体をアトミックにロード/ストアするように強制しません。(ただし、そうすることを妨げるものでvolatile
はありません。)データレースUBを回避するわけではありませんが、実際には、スレッド間通信には十分であり、(インラインasmと共に)手作業のアトミックを構築する方法でした。 C11 / C ++ 11より前、通常のCPUアーキテクチャー。彼らはしているキャッシュコヒーレントはそうvolatile
であるとほとんど同様の練習に_Atomic
とmemory_order_relaxed
純粋な負荷と純粋な店舗用、タイプのために使用されている場合、あなたが裂けを得ることはありませんので、コンパイラは、単一の命令を使用することを十分に絞り込みます。そしてもちろんvolatile
ISO C規格_Atomic
とmo_relaxed を使用して同じasmにコンパイルするコードを作成することの保証はありません。
あなたがした機能があった場合global_var++;
にint
、またはlong long
あなたがメインから実行することと非同期シグナルハンドラからの、それは、データ・レースUBを作成するために使用リエントラントへの道だろう。
コンパイル方法に応じて(メモリの宛先incまたはadd、またはload / inc / storeを分離するため)、同じスレッド内のシグナルハンドラーに関してアトミックであるかどうかが決まります。「int num」のnum ++をアトミックにすることはできますか?x86およびC ++での原子性についての詳細。(C11のstdatomic.h
and _Atomic
属性は、C ++ 11のstd::atomic<T>
テンプレートと同等の機能を提供します)
命令の途中で割り込みやその他の例外が発生することはないため、メモリ宛先の追加はアトミックです。シングルコアCPUのコンテキストスイッチ。(キャッシュコヒーレントな)DMAライターのみが、シングルコアCPUでのプレフィックスadd [mem], 1
なしのからのインクリメントを「踏む」ことができlock
ます。別のスレッドが実行されている可能性のある他のコアはありません。
したがって、シグナルの場合と同様です。シグナルを処理するスレッドの通常の実行の代わりにシグナルハンドラが実行されるため、1つの命令の途中で処理することはできません。