操作は、実際には新しい値を保存先に保存しない可能性があります。これは、しようとしているのと同時に値を変更する別のスレッドと競合するためです。CAS プリミティブは、書き込みが発生することを保証しません。値が既に期待どおりである場合にのみ、書き込みが発生します。プリミティブは、値が期待どおりでない場合、正しい動作が何であるかを知ることができないため、その場合は何も起こりません。戻り値をチェックして、操作が機能したかどうかを確認して問題を修正する必要があります。
だから、あなたの例:
elem->next = __sync_val_compare_and_swap(&head, head, elem); //always inserts?
必ずしも新しい要素を挿入するとは限りません。別のスレッドが同時に要素を挿入すると、競合状態が発生し、このスレッドの呼び出しが__sync_val_compare_and_swap()
更新されない可能性がありますhead
(ただし、正しく処理すれば、このスレッドの要素も他のスレッドの要素も失われません)。
ただし、そのコード行には別の問題があります。更新されたとしても、挿入された要素を指すhead
短い瞬間がありますhead
が、その要素のnext
ポインターはリストの前のヘッドを指すように更新されていません。その瞬間に別のスレッドが急襲してリストをたどろうとすると、悪いことが起こります。
リストを正しく更新するには、そのコード行を次のように変更します。
whatever_t* prev_head = NULL;
do {
elem->next = head; // set up `elem->head` so the list will still be linked
// correctly the instant the element is inserted
prev_head = __sync_val_compare_and_swap(&head, elem->next, elem);
} while (prev_head != elem->next);
またはbool
、もう少し便利だと思うバリアントを使用します。
do {
elem->next = head; // set up `elem->head` so the list will still be linked
// correctly the instant the element is inserted
} while (!__sync_bool_compare_and_swap(&head, elem->next, elem));
ちょっと醜いので、正しく理解できていることを願っています (スレッドセーフ コードの詳細につまずくのは簡単です)。関数でラップする必要がありinsert_element()
ます (または、適切なライブラリを使用することをお勧めします)。
ABA 問題への対処:
ABAの問題は、この「リストの先頭に要素を追加する」コードに関連しているとは思いません。X
スレッドがオブジェクトをリストに追加しようとしていてelem->next = head
、それが実行されたときにhead
値があるとしましょうA1
。
次に、__sync_val_compare_and_swap()
が実行される前に、別のスレッドのセットが来て、次のようになります。
- を指摘して
A1
、リストから削除しますhead
B
- オブジェクトに対して何でも行い
A1
、それを解放します
A2
たまたま同じアドレスにあるA1
別のオブジェクトを割り当てます
A2
をリストに追加するのでhead
、A2
A1
とは同じ識別子/アドレスを持っているためA2
、これは ABA 問題のインスタンスです。
ただし、オブジェクトを追加するスレッドは、開始時とは異なるオブジェクトへX
のポイントを気にしないため、この場合は問題ではありません。気にするのは、いつキューに入れられるかだけです。head
X
- リストは一貫しており、
- リスト上のオブジェクトが失われていない
X
(このスレッドによって) リストに追加された以外のオブジェクトはありません