与 CAS 的原子交换(使用 gcc 同步内置函数)

发布于 2024-09-04 16:04:21 字数 762 浏览 4 评论 0原文

比较和交换函数可以用于自动交换变量吗? 我在 x86_64 RedHat Linux 上通过 gcc 使用 C/C++,特别是 __sync 内置函数。 例子:

   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 和 head 就是这样):

   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

Can the compare-and-swap function be used to swap variables atomically?
I'm using C/C++ via gcc on x86_64 RedHat Linux, specifically the __sync builtins.
Example:

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

I think this boils down to whether x can change between &x and x; for instance, if &x constitutes an operation, it might be possible for x to change between &x and x in the arguments. I want to assume that the comparison implicit above will always be true; my question is whether I can. Obviously there's the bool version of CAS, but then I can't get the old x to write into y.

A more useful example might be inserting or removing from the head of a linked list (gcc claims to support pointer types, so assume that's what elem and head are):

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

望笑 2024-09-11 16:04:21

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

因此,您的示例:

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

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

但是,这行代码还有另一个问题 - 即使 head 确实更新了,也会有一小段时间 head 指向插入的元素,但该元素的 < code>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

因为 A1A2< /code> 具有相同的标识符/地址,这是 ABA 问题的一个实例。

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

  • 列表是一致的,
  • 列表上没有对象丢失,并且
  • 没有将X以外的对象添加到列表中(通过这个线程)

The operation might not actually store the new value into the destination because of a race with another thread that changes the value at the same moment you're trying to. The CAS primitive doesn't guarantee that the write occurs - only that the write occurs if the value is already what's expected. The primitive can't know what the correct behavior is if the value isn't what is expected, so nothing happens in that case - you need to fix up the problem by checking the return value to see if the operation worked.

So, your example:

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

won't necessarily insert the new element. If another thread inserts an element at the same moment, there's a race condition that might cause this thread's call to __sync_val_compare_and_swap() to not update head (but neither this thread's or the other thread's element is lost yet if you handle it correctly).

But, there's another problem with that line of code - even if head did get updated, there's a brief moment of time where head points to the inserted element, but that element's next pointer hasn't been updated to point to the previous head of the list. If another thread swoops in during that moment and tries to walk the list, bad things happen.

To correctly update the list change that line of code to something like:

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);

Or use the bool variant, which I think is a bit more convenient:

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));

It's kind of ugly, and I hope I got it right (it's easy to get tripped up in the details of thread-safe code). It should be wrapped in an insert_element() function (or even better, use an appropriate library).

Addressing the ABA problem:

I don't think the ABA problem is relevant to this "add an element to the head of a list" code. Let's say that a thread wants to add object X to the list and when it executes elem->next = head, head has value A1.

Then before the __sync_val_compare_and_swap() is executed, another set of threads comes along and:

  • removes A1 from the list, making head point to B
  • does whatever with object A1 and frees it
  • allocates another object, A2 that happens to to be at the same address as A1 was
  • adds A2 to the list so that head now points to A2

Since A1 and A2 have the same identifier/address, this is an instance of the ABA problem.

However, it doesn't matter in this case since the thread adding object X doesn't care that the head points to a different object than it started out with - all it cares about is that when X is queued:

  • the list is consistent,
  • no objects on the list have been lost, and
  • no objects other than X have been added to the list (by this thread)
青衫负雪 2024-09-11 16:04:21

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

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

至于x是否可以在&xx之间变化?是的,当然可以。

即使没有 &,它也可能会改变。

即使在像 Foo(x, x) 这样的函数中,您也可以获得两个不同的 x 值,因为为了调用该函数,编译器必须:

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

这两个操作之间的调用约定,另一个线程可以轻松修改x<的值/代码>。

Nope. The CAS instruction on x86 takes a value from a register, and compares/writes it against a value in memory.

In order to atomically swap two variables, it'd have to work with two memory operands.

As for whether x can change between &x and x? Yes, of course it can.

Even without the &, it could change.

Even in a function such as Foo(x, x), you could get two different values of x, since in order to call the function, the compiler has to:

  • take the value of x, and store it in the first parameter's position, according to the calling convention
  • take the value of x, and store it in the second parameter's position, according to the calling convention

between those two operations, another thread could easily modify the value of x.

梦里人 2024-09-11 16:04:21

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

但是,您仍然遇到 y 赋值之间的竞争条件问题。有时 y 是本地的,在这种情况下这将是安全的,但如果 xy 都共享,则会出现重大问题,并且将需要一个锁来解决它。

It seems like you're looking for the interlocked-exchange primitive, not the interlocked-compare-exchange. That will unconditionally atomically swap the holding register with the target memory location.

However, you still have a problem with race conditions between assignments to y. Sometimes y is a local, in which case this will be safe, but if both x and y are shared you have a major problem and will need a lock to resolve it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文