简体   繁体   中英

Are C++ atomics preemption safe?

From what I understand of atomics, they are special assembly instructions which guarantee that two processors in an SMP system cannot write to the same memory region at the same time. For example in PowerPC an atomic increment would look something like:

retry:
  lwarx  r4, 0, r3 // Read integer from RAM location r3 into r4, placing reservation.
  addi   r4, r4, 1 // Add 1 to r4.
  stwcx. r4, 0, r3 // Attempt to store incremented value back to RAM.
  bne-   retry     // If the store failed (unlikely), retry.

However this does not protect the four instructions from begin preempted by an interrupt, and another task being scheduled in. To protect from preemption you need to do an interrupt-lock before entering the code.

From what I can see of C++ atomics , they seem to be enforcing locks if needed. So my first question is -

  1. Does the C++ standard guarantee no preemption will happen during an atomic operation? If so, where in the standard can I find this?

I checked the atomic<int>::is_always_lock_free on my Intel PC, and it came true . With my assumption of the above assembly block, this confused me. After digging into Intel assembly (which I am unfamiliar with) I found lock xadd DWORD PTR [rdx], eax to be happening. So my question is -

  1. Do some architectures provide atomic related instructions which guarantee no-preepmtion? Or is my understanding wrong?

Finally I was wondering about the compare_exchange_weak and compare_exchange_strong semantics -

  1. Does the difference lie int the retry mechanism or is it something else?

EDIT : After reading the answers, I am curious about one more thing

  1. The atomic member function operations fetch_add , operator++ etc. are they strong or weak?

Does the C++ standard guarantee no preemption will happen during an atomic operation? If so, where in the standard can I find this?

No, it doesn't. Since there's really no way code could tell whether or not this happened (it's indistinguishable from pre-emption either before or after the atomic operation depending on circumstances) there is no reason for it to.

Do some architectures provide atomic related instructions which guarantee no-preemption? Or is my understanding wrong?

There would be no point since the operation must appear to be atomic anyway, so pre-emption during would always be identical in observed behavior to pre-emption before or after. If you can write code that ever sees a case where pre-emption during the atomic operation causes observable effects different from either pre-emption before or pre-emption after, that platform is broken since the operation does not behave atomically.

This is similar to this question: Anything in std::atomic is wait-free?

Here are some definitions of lock-freedom and wait-freedom (both taken from Wikipedia ):

An algorithm is lock-free if, when the program threads are run for a sufficiently long time, at least one of the threads makes progress.

An algorithm is wait-free if every operation has a bound on the number of steps the algorithm will take before the operation completes.

Your code with the retry loop is lock-free: a thread only has to perform a retry if the store fails, but that implies that the value must have been updated in the meantime, so some other thread must have made progress.

With regard to lock-freedom it doesn't matter whether a thread can be preempted in the middle of an atomic operation or not.

Some operations can be translated to a single atomic operation, in which case this operation is wait-free and therefore cannot be preempted midway. However, which operations are actually wait-free depends on the compiler and the target architecture (as described in my answer in the referenced SO question).

Regarding the difference between compare_exchange_weak and compare_exchange_strong - the weak version can fail spuriously, ie, it may fail even though the comparison is actually true. This can happen on architectures with LL/SC. Suppose we use compare_exchange_weak to update some variable with the expected value A . LL loads the value A from the variable, and before the SC is executed, the variable is changed to B and then back to A . So even though the variable contains the same value as before, the intermediate change to B causes the SC (and therefore the compare_exchange_weak ) to fail. compare_exchange_strong cannot fail spuriously, but to achieve that it has to use a retry-loop on architectures with LL/SC.

I am not entirely sure what you mean by fetch_add being "strong or weak". fetch_add cannot fail - it simply performs an atomic update of some variable by adding the provided value, and returns the old value of the variable. Whether this can be translated to a single instruction (like on Intel) or to a retry loop with LL/SC (Power) or CAS (Sparc) depends on the target architecture. Either way, the variable is guaranteed to be updated correctly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM