12

There are two atomic CAS operations in C++11: atomic_compare_exchange_weak and atomic_compare_exchange_strong.

According to cppreference:

The weak forms of the functions are allowed to fail spuriously, that is, act as if *obj != *expected even if they are equal. 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 following is an example for using the weak version, I think:

do {
    expected = current.value();
    desired = f(expected);
} while (!current.atomic_compare_exchange_weak(expected, desired));

Could someone give an example where the compare-and-exchange is not in a loop so that the strong version is preferable?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
user571470
  • 394
  • 4
  • 14
  • 1
    It seems like this could come up whenever the operation wasn't critical. Let's say that you have a number of worker threads that have some regular work they are doing, but they are also checking for additional work in a queue. If the CAS operation makes it seem like the queue can't be locked, it may not be a big deal, since the worker can continue to do its regular work. – Vaughn Cato Jul 29 '13 at 01:17
  • Maybe you can refer to [Understanding std::atomic::compare_exchange_weak() in C++11](https://stackoverflow.com/questions/25199838/understanding-stdatomiccompare-exchange-weak-in-c11) which gave more explanation about what's said in C++11 Standard (ISO/IEC 14882) and [How do I choose between the strong and weak versions of compare-exchange?](https://devblogs.microsoft.com/oldnewthing/20180330-00/?p=98395). Choosing the strong version over the weak version comes down to whether spurious failures are acceptable and how expensive they are if you care. – samm Jan 07 '22 at 02:56

1 Answers1

15

The atomic_compare_exchange_XXX functions update their "expected" argument with the observed value, so your loop is the same as:

expected = current;
do {
    desired = f(expected);
} while (!current.atomic_compare_exchange_weak(expected, desired));

If the desired value is independent of the expected value, this loop becomes:

desired = ...;
expected = current;
while (current.atomic_compare_exchange_weak(expected, desired))
  ;

Let's add some semantics. Let's say that several threads are running this simultaneously. In each case desired is a non-zero ID for the current thread, and current is used to provide mutual exclusion to ensure that some thread performs a cleanup task. We don't really care which one, but we want to be sure that some thread gets access (and maybe other threads can observe the winner by reading it's ID from current).

We can achieve the desired semantics with:

expected = 0;
if (current.atomic_compare_exchange_strong(expected, this_thread)) {
  // I'm the winner
  do_some_cleanup_thing();
  current = 0;
} else {
  std::cout << expected << " is the winner\n";
}

This is a case where atomic_compare_exchange_weak would require a loop to accomplish the same effect as atomic_compare_exchange_strong, since spurious failures are possible:

expected = 0;
while(!current.atomic_compare_exchange_weak(expected, this_thread)
       && expected == 0))
  ;
if (expected == this_thread) {
  do_some_cleanup_thing();
  current = 0;
} else {
  std::cout << expected << " is the winner\n";
}

The standard suggests that implementations may provide more efficient code in this case for atomic_compare_exchange_strong than looping with ..._weak (atomics.types.operations/25).

S.V
  • 2,149
  • 2
  • 18
  • 41
Casey
  • 41,449
  • 7
  • 95
  • 125