该操作实际上可能不会将新值存储到目标中,因为在您尝试更改值的同时与另一个线程竞争。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
被添加到列表中(由这个线程)