Namespaces
Variants
Views
Actions

Talk:cpp/atomic/atomic thread fence

From cppreference.com

Am I the only one to wonder at that description? ;) Maybe most of it comes from the standard rationale, but seriously, that single sentence is not only ugly, it's kinda... frightening.

atomics are frightening! But yes, if you are referring to "For example, all non-atomic and relaxed atomic stores that happen before a std::memory_order_release fence in thread A will be synchronized with non-atomic and relaxed atomic loads from the same locations made in thread B after an std::memory_order_acquire fence if there exist atomic operations X and Y, both operating on some atomic object M, such that the fence in thread A is sequenced before X, X modifies M, Y is sequenced before the fence in thread B, and Y reads the value written by X or a value written by any side effect in the hypothetical release sequence X would head if it were a release operation..", it is far too long for a sentence. Re-written. --Cubbi (talk) 18:52, 14 June 2016 (PDT)

Is the last sentence under Fence-atomic Synchronization incorrect? Should it rather say:

In this case, all non-atomic and relaxed atomic stores that happen-before F [not X] in thread A will be synchronized-with all non-atomic and relaxed atomic loads from the same locations made in thread B after Y.

--Paul Licameli (talk) 16:15, 21 June 2018 (EDT)

Fixed. T. Canens (talk) 13:32, 21 June 2018 (PDT)

[edit] improving the head section

I'm trying to think of an intuitive explanation that would be shorter but encompass the long detailed formality of the fence-op/op-fence/fence-fence. Possibly it wouldn’t be precisely formal, but it should be short enough to come in the head section. How about:

“A release fence has the effect on all subsequent atomic stores in the thread, making them all release operations w.r.t. writes that were sequenced before the fence. Also, no writes can be reordered after the release fence. Similarly, an acquire fence has the effect on all preceding atomic loads in the thread, making them all acquire operations w.r.t. reads that were sequenced after the fence. Also, no reads can be reordered before the acquire fence.”--Itaj (talk) 16:10, 8 August 2016 (PDT)

Well, it's not good enough for the head yet.--Itaj (talk) 16:18, 8 August 2016 (PDT)
Another try:
”An acquire fence synchronizes with all release atomic-stores operations and release fences that happen before it. Similarly, a release fence synchronizes with all acquire atomic-load operations and acquire fences that happen after it.”
This description informally uses the happen-before relation as defined by atomic-operations, and adds new synchronizations which in turn define a more refined happen-before relation.--Itaj (talk) 16:30, 8 August 2016 (PDT)
Yeah, these are both logically wrong, somehow combining them might result in a correct claim. I guess it's really not straight forward.--Itaj (talk) 16:34, 8 August 2016 (PDT)
An informal intro could probably be formulated in terms of allowed instruction reorderings, since that is the traditional meaning of "barrier". --Cubbi (talk) 19:33, 8 August 2016 (PDT)

[edit] Shouldn't release fence be sequenced after atomic write ?

Fence-fence synchronization A release fence FA in thread A synchronizes-with an acquire fence FB in thread B, if

There exists an atomic object M, There exists an atomic write X (with any memory order) that modifies M in thread A FA is sequenced-before X in thread A There exists an atomic read Y (with any memory order) in thread B Y reads the value written by X (or the value would be written by release sequence headed by X if X were a release operation) Y is sequenced-before FB in thread B

[edit] Can we add a diagram

I will need some help to get the diagram correct. For example, for fence-fence synchronization I've got:

non-atomic/relaxed STORES ||
cannot reorder forwards   || 
                    Fence ||
--------------------------||--S(M)---------------------------- Thread A
                              Atomic STORE to variable M
                                           Atomic LOAD from variable M
-------------------------------------------L(M)----||--------- Thread B
                                                   || Fence
                                                   || non-atomic/relaxed LOADS
                                                   || cannot reorder backwards

Amichaux (talk) 09:12, 16 March 2022 (PDT)

rephrasing the dependency graphs in terms of permitted compiler and hardware reordering is always tempting (because it is less abstract), but very hard to do. I think examples with commentary serve as a better illustration. Also, for this case, reordering affects both loads and stores that sequence before the release fence, and they can be reordered past the fence, just not past any subsequent store (note thread-thread example uses three stores to illustrate the need; with just one atomic as in the diagram the standalone fence is a pessimization ) --Cubbi (talk) 11:06, 16 March 2022 (PDT)

Okay, understood. However, I've seen good developers in multiple organizations treat atomic_thread_fence like it'll emit x86_64 instructions "lock ...", "sfence", "mfence", etc. This page is -- obviously -- accurate; however, it's also confusing. Maybe we can have a simple simple simple example, like so?

struct Fence {
public:
  Fence() = default;
  ~Fence() = default;

  void lock() { try_lock(); } // always succeeds

  bool try_lock() {
    std::atomic_thread_fence(std::memory_order_acquire);
    sync_.fetch_add(1, std::memory_order_acquire);
  }
 
  void unlock() {
    sync_.fetch_add(-1, std::memory_order_release);
    std::atomic_thread_fence(std::memory_order_release);
  }

private:
  std::atomic<int32_t> sync_{0};
};


Fence fence;
{
  std::lock_guard lock{fence};
  // everything in this code block is protected by the fence.
  int x = 4; // This is a store... so no loads/stores before the
             // fence can be sequenced after this line.

  getValue(); // This is a load... so no loads/stores after the fence
              // can be sequenced before this line
}

Amichaux (talk) 10:40, 17 March 2022 (PDT)

if you make the atomic op relaxed, you're approaching the first mini-example in Preshing's article. Regarding your mention of mfence, fighting misconceptions is a good idea, let me actually make an explicit note on when these functions issue CPU instructions --Cubbi (talk) 12:13, 17 March 2022 (PDT)

That clears up one common misconception. The next most common thing I've seen is the belief that std::atomic_thread_fence does anything without an atomic operation involved. If you carefully read the first sentence of this page, you might see that someone could come to that opinion. The error is demonstrated in this stack overflow post. Alas, but I've seen this happen in two different mission critical code bases! Can we have an additional note that points out the requirement of an atomic operation for the fences to synchronize around? Amichaux (talk) 18:38, 17 March 2022 (PDT)

Suggested addition -- after the "Notes" headline, but before the comment on machine instructions. "Although std::atomic_thread_fence affects memory synchronizing ording of non-atomic operations, it has no effect without some associated atomic operation (load/store) about which to synchronize. See Fence-Fence synchronization above" Amichaux (talk) 10:20, 18 March 2022 (PDT)