由于与另一个同时更改目标值的线程产生竞争,该操作可能实际上不会将新值存储到目标中。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
(由此线程)之外,没有添加其他对象到列表中