使用CAS进行原子交换(使用gcc sync builtins)

时间:2010-06-04 15:20:45

标签: c++ gcc linked-list atomic compare-and-swap

比较和交换功能可以用于原子交换变量吗? 我在x86_64 RedHat Linux上通过gcc使用C / C ++,特别是__sync builtins。 例如:

   int x = 0, y = 1; 
   y = __sync_val_compare_and_swap(&x, x, y);

我认为这归结为x是否可以在& x和x之间变换;例如,如果& x构成一个操作,则x可能在参数中的& x和x之间变化。我想假设上面隐含的比较总是正确的;我的问题是我是否可以。显然有CAS的bool版本,但是我不能让旧的x写入y。

一个更有用的例子可能是从链接列表的头部插入或删除(gcc声称支持指针类型,所以假设这是元素和头部):

   elem->next = __sync_val_compare_and_swap(&head, head, elem); //always inserts?
   elem = __sync_val_compare_and_swap(&head, head, elem->next); //always removes?

参考: http://gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html

3 个答案:

答案 0 :(得分:3)

该操作实际上可能不会将新值存储到目标中,因为与另一个线程的竞争会在您尝试的同一时刻更改该值。 CAS原语不保证写入发生 - 只有当值已经是预期值时才发生写入。如果值不是预期值,则原语无法知道正确的行为是什么,因此在这种情况下没有任何反应 - 您需要通过检查返回值来确定操作是否有效来解决问题。

所以,你的例子:

elem->next = __sync_val_compare_and_swap(&head, head, elem); //always inserts?

不一定会插入新元素。如果另一个线程同时插入一个元素,那么可能会导致该线程对__sync_val_compare_and_swap()的调用不更新head的竞争条件(但是如果你这个线程或其他线程的元素都没有丢失)正确处理它)。

但是,这行代码存在另一个问题 - 即使head确实得到更新,head指向插入元素的时间很短,但该元素的{{1} }指针尚未更新为指向列表的上一个头部。如果另一个线程在那一刻猛扑进去并试图走在列表中,就会发生不好的事情。

要正确更新列表,请将该行代码更改为:

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)); 函数中(或者更好,使用适当的库)。

解决ABA问题:

我不认为ABA问题与“将元素添加到列表头部”代码相关。假设一个线程想要将对象insert_element()添加到列表中,并且当它执行X时,elem->next = head具有值head

然后在执行A1之前,又出现了另一组线程:

  • 从列表中删除__sync_val_compare_and_swap(),使A1指向head
  • 对象B执行任何操作并释放它
  • 分配另一个对象,A1碰巧与A2所在的地址相同
  • A1添加到列表中,以便A2现在指向head

由于A2A1具有相同的标识符/地址,因此这是ABA问题的一个实例。

但是,在这种情况下并不重要,因为线程添加对象A2并不关心X指向的是与其开始时不同的对象 - 所有它关心的是当head排队时:

  • 列表一致,
  • 列表中没有任何对象丢失,
  • 列表中没有添加X以外的任何对象(通过此主题)

答案 1 :(得分:0)

不。 x86上的CAS指令从寄存器中获取值,并将其与内存中的值进行比较/写入。

为了原子地交换两个变量,它必须使用两个内存操作数。

至于x&x之间x是否可以更改?是的,当然可以。

即使没有&,也可能会发生变化。

即使在诸如Foo(x, x)之类的函数中,也可以得到两个不同的x值,因为为了调用该函数,编译器必须:

  • 取x的值,并根据调用约定
  • 将其存储在第一个参数的位置
  • 取x的值,并根据调用约定
  • 将其存储在第二个参数的位置

在这两个操作之间,另一个线程可以轻松修改x

的值

答案 2 :(得分:0)

看起来你正在寻找的是互锁交换原语,而不是互锁比较交换。这将无条件地将保持寄存器与目标存储器位置原子交换。

但是,对y的作业之间的竞争条件仍有问题。有时y是本地的,在这种情况下这是安全的,但如果共享xy,则会遇到重大问题,需要锁定才能解决问题。