liveBook · Manning

Release operation

As explained in C++ Concurrency in Action, Second Edition

A release operation is an atomic operation whose ordering can synchronize with an acquire operation on the same memory location.

A release operation makes earlier work available to another thread when an acquire operation on the same atomic location synchronizes with it. The earlier work can include writes to non-atomic data: those writes are sequenced before the release and, through the release/acquire pair, happen before the acquiring thread’s later accesses. Release ordering alone does not establish that relationship; the corresponding acquire matters. section 5.3.7

Unlocking a spinlock

A spinlock’s lock() repeatedly calls flag.test_and_set(std::memory_order_acquire). Its unlock() calls flag.clear(std::memory_order_release). After one thread changes protected data and clears the flag, another thread’s successful acquisition synchronizes with that release. The protected changes therefore happen before the second thread accesses the data. section 5.3.7

void push(T const& data)
{
    counted_node_ptr new_node;
    new_node.ptr=new node(data);
    new_node.external_count=1;
    new_node.ptr->next=head.load(std::memory_order_relaxed)
    while(!head.compare_exchange_weak(new_node.ptr->next,new_node,
        std::memory_order_release,std::memory_order_relaxed));
}

In the stack’s push(), node construction and the write to next precede the successful compare_exchange_weak that publishes the node through head. That success needs release ordering so a later acquiring pop() can safely read next. A failed exchange publishes nothing, so its failure ordering is relaxed. section 7.2.5

void increase_head_count(counted_node_ptr& old_counter)
{
    counted_node_ptr new_counter;
    do
    {
        new_counter=old_counter;
        ++new_counter.external_count;
    }
    while(!head.compare_exchange_strong(old_counter,new_counter,
        std::memory_order_acquire,std::memory_order_relaxed));
    old_counter.external_count=new_counter.external_count;
}

The pop-side compare_exchange_strong uses acquire ordering on success. When it reads the head value published by push(), the release and acquire establish the happens-before relationship needed before accessing the node’s non-atomic next field. Failure can use relaxed ordering because the loop retries. section 7.2.5

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Traces sequenced-before, synchronizes-with, and happens-before through a spinlock and successful or failed stack compare/exchange operations.
Examples
Uses an atomic_flag spinlock and a reference-counted lock-free stack whose compare/exchange operations distinguish success from failure ordering.
What it assumes
Assumes familiarity with C++ atomic operations while explaining how their ordering makes earlier non-atomic writes safe to access across threads.
What you can do after
Apply it via C++ atomic memory orders to publish data with release and require acquire before reading dependent non-atomic fields.
How it connects
Connects release operations to acquire operations, happens-before, mutexes, condition variables, and lock-free stacks.

Also explained in

Compare how 2 books explain release operation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.