std::atomic_thread_fence(3)

πŸ“¦ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

okay so bestie, welcome to std::atomic_thread_fence(3) β€” the std::atomic_thread_fence page β€” straight outta the C++ Standard Libary, and we are about to make fences make sense πŸ’…βœ¨

πŸ’… NAME πŸ’…

std::atomic_thread_fence - std::atomic_thread_fence 😌 like yes, the name is literally the name, she said what she said πŸŽ€

✨ Synopsis ✨

Defined in header <atomic>:

extern "C" void atomic_thread_fence( std::memory_order order )

(since C++11) and yes, it's noexcept; 🫢

okay so what does she do? std::atomic_thread_fence establishes memory synchronization ordering of non-atomic and relaxed atomic accesses, as instructed by order, without an associated atomic operation. read that again: no associated atomic op 😭. but β€” important plot twist β€” at least one atomic operation is required to set up the synchronization, as described below. that's the whole vibe, no cap πŸ’–

πŸ’– Parameters πŸ’–

  • order β€” the memory ordering executed by this fence. that's it, that's the parameter πŸŽ€

🫢 Return value 🫢

(none) β€” literally nothing comes back. return value? never met her πŸ’€

🌈 Notes 🌈

On x86 (including x86-64), atomic_thread_fence functions issue no CPU instructions and only affect compile-time code motion, except for std::atomic_thread_fence(std::memory_order::seq_cst). so yeah, mostly a compile-time vibe check, except that one special seq_cst girlie ✨

also, atomic_thread_fence imposes stronger synchronization constraints than an atomic store operation with the same std::memory_order. While an atomic store-release operation prevents all preceding reads and writes from moving past the store-release, an atomic_thread_fence with memory_order_release ordering prevents all preceding reads and writes from moving past all subsequent stores. that distinction is lowkey iconic, okay? πŸ”₯

Fence-fence synchronization can be used to add synchronization to a sequence of several relaxed atomic operations, for example:

// Global
std::string computation(int);
void print(std::string);

std::atomic<int> arr[3] = {-1, -1, -1};
std::string data[1000]; //non-atomic data

// Thread A, compute 3 values.
void ThreadA(int v0, int v1, int v2)
{
//  assert(0 <= v0, v1, v2 < 1000);
    data[v0] = computation(v0);
    data[v1] = computation(v1);
    data[v2] = computation(v2);
    std::atomic_thread_fence(std::memory_order_release);
    std::atomic_store_explicit(&arr[0], v0, std::memory_order_relaxed);
    std::atomic_store_explicit(&arr[1], v1, std::memory_order_relaxed);
    std::atomic_store_explicit(&arr[2], v2, std::memory_order_relaxed);
}

// Thread B, prints between 0 and 3 values already computed.
void ThreadB()
{
    int v0 = std::atomic_load_explicit(&arr[0], std::memory_order_relaxed);
    int v1 = std::atomic_load_explicit(&arr[1], std::memory_order_relaxed);
    int v2 = std::atomic_load_explicit(&arr[2], std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);
//  v0, v1, v2 might turn out to be -1, some or all of them.
//  Otherwise it is safe to read the non-atomic data because of the fences:
    if (v0 != -1)
        print(data[v0]);
    if (v1 != -1)
        print(data[v1]);
    if (v2 != -1)
        print(data[v2]);
}

πŸ”₯ Example πŸ”₯

Scan an array of mailboxes, and process only the ones intended for us, without unnecessary synchronization. This example uses atomic-fence synchronization. it's giving practical and cute πŸ¦‹

const int num_mailboxes = 32;
std::atomic<int> mailbox_receiver[num_mailboxes];
std::string mailbox_data[num_mailboxes];

// The writer threads update non-atomic shared data
// and then update mailbox_receiver[i] as follows:
mailbox_data[i] = ...;
std::atomic_store_explicit(&mailbox_receiver[i], receiver_id, std::memory_order_release);

// Reader thread needs to check all mailbox[i], but only needs to sync with one.
for (int i = 0; i < num_mailboxes; ++i)
    if (std::atomic_load_explicit(&mailbox_receiver[i],
        std::memory_order_relaxed) == my_id)
    {
        // synchronize with just one writer
        std::atomic_thread_fence(std::memory_order_acquire);
        // guaranteed to observe everything done in the writer thread
        // before the atomic_store_explicit()
        do_work(mailbox_data[i]);
    }

πŸ‘‘ See also πŸ‘‘

  • memory_order (C++11) β€” defines memory ordering constraints for the given atomic operation; it's an enum 🌸
  • atomic_signal_fence (C++11) β€” fence between a thread and a signal handler executed in the same thread; it's a function 🫢
  • C documentation for atomic_thread_fence β€” the C docs, for when you wanna cross-reference, iykyk πŸ’œ

mwah πŸ’– this glow-up comes from source/version http://cppreference.com, dated 2024.06.10 β€” now go forth and synchronize, bestie! πŸ’…βœ¨

πŸ“¦ where this page comes from