47

I'm unsure if it's me not understanding or the documentation isn't clearly formulated.

The following excerpt has been taken from the newest draft (N3126, section 29.6):

bool atomic_compare_exchange_weak(volatile A* object, C * expected, C desired);
bool atomic_compare_exchange_weak(A* object, C * expected, C desired);
bool atomic_compare_exchange_strong(volatile A* object, C * expected, C desired);
bool atomic_compare_exchange_strong(A* object, C * expected, C desired);
bool atomic_compare_exchange_weak_explicit(volatile A* object, C * expected, C desired, memory_order success, memory_order failure);
bool atomic_compare_exchange_weak_explicit(A* object, C * expected, C desired, memory_order success, memory_order failure);
bool atomic_compare_exchange_strong_explicit(volatile A* object, C * expected, C desired, memory_order success, memory_order failure);
bool atomic_compare_exchange_strong_explicit(A* object, C * expected, C desired, memory_order success, memory_order failure);
bool A::compare_exchange_weak(C & expected, C desired, memory_order success, memory_order failure) volatile;
bool A::compare_exchange_weak(C & expected, C desired, memory_order success, memory_order failure);
bool A::compare_exchange_strong(C & expected, C desired, memory_order success, memory_order failure) volatile;
bool A::compare_exchange_strong(C & expected, C desired, memory_order success, memory_order failure);
bool A::compare_exchange_weak(C & expected, C desired, memory_order order = memory_order_seq_cst) volatile;
bool A::compare_exchange_weak(C & expected, C desired, memory_order order = memory_order_seq_cst);
bool A::compare_exchange_strong(C & expected, C desired, memory_order order = memory_order_seq_cst) volatile;
bool A::compare_exchange_strong(C & expected, C desired, memory_order order = memory_order_seq_cst);

Remark: The weak compare-and-exchange operations may fail spuriously, that is, return false while leaving the contents of memory pointed to by expected before the operation is the same that same as that of the object and the same as that of expected after the operation. [ Note: This spurious failure enables implementation of compare-and-exchange on a broader class of machines, e.g., loadlocked store-conditional machines. A consequence of spurious failure is that nearly all uses of weak compare-and-exchange will be in a loop.

So, what does this mean?

Firstly, it 'may' fail spuriously?! Why would it fail? And how do they define 'may'?

Secondly, I still have no idea what's the difference between the functions with "_strong" and "_weak" suffix. Could someone explain the difference?

EDIT: That's what I've found in libstdc++-implementation (atomic_0.h):

bool compare_exchange_weak(
    __integral_type& __i1,
    __integral_type __i2,
    memory_order __m1,
    memory_order __m2
)
{
    __glibcxx_assert(__m2 != memory_order_release);
    __glibcxx_assert(__m2 != memory_order_acq_rel);
    __glibcxx_assert(__m2 <= __m1);
    return _ATOMIC_CMPEXCHNG_(this, &__i1, __i2, __m1);
}

bool compare_exchange_strong(
    __integral_type& __i1,
    __integral_type __i2,
    memory_order __m1,
    memory_order __m2
)
{
    __glibcxx_assert(__m2 != memory_order_release);
    __glibcxx_assert(__m2 != memory_order_acq_rel);
    __glibcxx_assert(__m2 <= __m1);
    return _ATOMIC_CMPEXCHNG_(this, &__i1, __i2, __m1);
}
Richard Chambers
  • 16,643
  • 4
  • 81
  • 106
0xbadf00d
  • 17,405
  • 15
  • 67
  • 107
  • I added the STL tag, with hope it'll bring Howard Hinnant there, he's been working on implementing these in libc++ so he should know about it. – Matthieu M. Feb 09 '11 at 12:30

2 Answers2

53

The note gives a clue, referring to LL/SC architectures. From the Wikipedia article:

If any updates have occurred, the store-conditional is guaranteed to fail, even if the value read by the load-link has since been restored. As such, an LL/SC pair is stronger than a read followed by a compare-and-swap (CAS), which will not detect updates if the old value has been restored (see ABA problem).

Real implementations of LL/SC do not always succeed if there are no concurrent updates to the memory location in question. Any exceptional events between the two operations, such as a context switch, another load-link, or even (on many platforms) another load or store operation, will cause the store-conditional to spuriously fail.

On LL/SC chips the compare_exchange will be implemented in terms of LL/SC, which can spuriously fail, so compare_exchange_strong needs extra overhead to retry in the case of failure. Providing both compare_exchange_strong and compare_exchange_weak allows the programmer to decide whether they want the library to handle spurious failures (in which case they'd use compare_exchange_strong) or if they want to handle it in their own code (in which case they'd use compare_exchange_weak)

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Nice answer. One thing to discuss is about "retry in the case of failure", should it be "retry in the case of ***spurious*** failure"? If that's a true failure due to contention (dest value not equal to the expected), `compare_exchange_strong` should return `false` immediately w/o any retry. Do you agree? – Eric Z Aug 08 '14 at 08:35
  • I posted a question regarding `compare_exchange_weak` [here](http://stackoverflow.com/questions/25199838/understanding-of-stdatomiccompare-exchange-weak-in-c11). Would you mind taking a look and sharing some knowledge you know with us? Thanks. – Eric Z Aug 08 '14 at 09:12
  • Yes, the strong form retries in case of spurious failure, not when the object doesn't have the expected value. – Jonathan Wakely Aug 08 '14 at 09:27
  • 1
    I came here from https://stackoverflow.com/a/16190791 . It's a question of implementing atomic_max function. I think it's a good example of using the `_weak` case. The maximum value may fail to update due to spurious failure, or due to another thread updating the value as well. However, if both cases you want to do the same: try to compute the maximum again. Using `_strong` would be inefficient on some machines, as you don't really care about spurious failure case specifically. – CygnusX1 Dec 04 '20 at 17:17
  • 3
    @CygnusX1 yes, that is covered in the C++ standard by the rest of the note, which OP left out of the quote: "When a compare-and-exchange is in a loop, the weak version will yield better performance on some platforms. When a weak compare-and-exchange would require a loop and a strong one would not, the strong one is preferable." The corollary is that when you're going to loop anyway, there's no reason to prefer the strong one. – Jonathan Wakely Dec 07 '20 at 11:48
  • 1
    @JonathanWakely I don't see how your "corollary" (the converse of the quote) follows. And it seems to me it's not true: consider the case when the loop body contains a somewhat expensive computation. Using the strong operation would make that expensive computation less likely to happen, which could be a win, right? – Don Hatch Aug 29 '21 at 11:02
  • @DonHatch the usage of the weak version assumes that the `expected` value can be safely dismissed since something is already occupying the locked cache, potentially changing its value on the way out. IMO the only point in which a strong CAS is valid is for situations in which the expected value is either a constant, true or a false, or an enum, if the value will be updated from a previous state, then it should just fail in the presence of contention since the first read will most certainly fail the `cmp` either way. (This opinion comes from a higher-level perspective). – Delark Sep 02 '23 at 02:02
13

It has to do with the shared memory consistency model that the hardware implements. For those hardware architectures that implement some kind of relaxed consistency model (e.g. release semantics), the strong operations you refer to above can have a high overhead, and thus experts can use the weaker forms to implement algorithms that perform well also on those relaxed consistency architectures.

For more info, see e.g.

http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-95-7.pdf

Chapter 12 and Appendix C in http://kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html

janneb
  • 36,249
  • 2
  • 81
  • 97
  • 2
    But, isn't it the sense of memory_order parameter to strengthen or weaken the memory-ordering model? – 0xbadf00d Feb 09 '11 at 14:34
  • No, that is specified by the hardware. That parameter specifies the minimum ordering your algorithm requires in order to operate correctly. It is then up the C++0x implementation to make sure that at least the specified ordering is provided; for strongly ordered architectures like x86, it's likely that both the strong and weak implementations are the same. – janneb Feb 09 '11 at 15:56
  • Sry, but it still doesn't make sense to me. Isn't it redundant to specify the ordering via memory_order parameter and strong or weak methods? Maybe it would help to know what's the main (abstract) difference between weak and strong compare_exchange. – 0xbadf00d Feb 09 '11 at 16:42
  • 9
    @FrEEzE: The difference is exactly what the standard says - that the weak version is allowed to fail occasionally, if that makes it run faster on your hardware. Not applicable for x86. – Bo Persson Feb 09 '11 at 21:44