Scheffの答えは、コードを修正する方法を説明しています。この場合、実際に何が起こっているかについて少し情報を追加したいと思いました。
最適化レベル1()を使用して、コードをgodboltでコンパイルしました-O1
。関数は次のようにコンパイルされます。
func():
cmp BYTE PTR finished[rip], 0
jne .L4
.L5:
jmp .L5
.L4:
mov eax, 0
ret
それで、ここで何が起こっているのですか?まず、比較があります:cmp BYTE PTR finished[rip], 0
-これfinished
はがfalseかどうかをチェックします。
false(別名true)でない場合は、最初の実行でループを終了する必要があります。これが達成さによってjne .L4
どのjは umps N OT EのラベルにQUAL .L4
の値は、ここでi
(0
)後の使用および機能が戻るためのレジスタに格納されます。
ただし、それが偽の場合は、
.L5:
jmp .L5
これは無条件のジャンプであり、.L5
偶然にもジャンプコマンド自体にラベルを付けます。
つまり、スレッドは無限ビジーループに入ります。
なぜこれが起こったのですか?
オプティマイザに関する限り、スレッドはその対象外です。他のスレッドが同時に変数を読み書きしていないことを前提としています(データレースUBであるため)。アクセスを離れて最適化できないことを伝える必要があります。これがScheffの答えが出てくるところです。私は彼を繰り返すことに迷惑を掛けません。
オプティマイザは、finished
変数が関数の実行中に変更される可能性があることを知らされていないため、finished
関数自体によって変更されていないことがわかり、定数であると想定します。
最適化されたコードは、定数のブール値を使用して関数を入力した結果として生じる2つのコードパスを提供します。ループが無限に実行されるか、ループが実行されることはありません。
で、-O0
(予想通り)コンパイラ離れループ本体との比較を最適化しません。
func():
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], 0
.L148:
movzx eax, BYTE PTR finished[rip]
test al, al
jne .L147
add QWORD PTR [rbp-8], 1
jmp .L148
.L147:
mov rax, QWORD PTR [rbp-8]
pop rbp
ret
したがって、最適化されていない関数が機能する場合、コードとデータ型が単純であるため、ここでの原子性の欠如は通常問題ではありません。おそらく、ここで遭遇する可能性のある最悪の事態は、本来あるべき値からi
1つずれた値です。
データ構造を備えたより複雑なシステムは、データの破損や不適切な実行を引き起こす可能性がはるかに高くなります。