liveBook · Manning

Release sequence

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

A release sequence is a sequence of operations on an atomic object beginning with a release operation and continuing through qualifying read-modify-write operations that read the preceding value.

A release sequence begins with an atomic release operation and continues through read-modify-write operations, each reading the value written by the preceding operation. It can carry synchronization from the initial operation to a later load. section 5.3.4

The synchronization guarantee depends on the memory ordering:

  • The initial store uses memory_order_release, memory_order_acq_rel, or memory_order_seq_cst; read-modify-write operations in the sequence may use any ordering, including relaxed. section 5.3.4
  • The final load uses memory_order_acquire or memory_order_seq_cst to synchronize with the initial store; memory_order_consume makes it dependency-ordered-before the load. section 5.3.4

A shared queue count

A producer fills a shared buffer and stores the item count with release ordering. Consumers claim items using acquire fetch_sub operations. Even when a later consumer reads a value written by an earlier fetch_sub rather than by the producer’s store, the release sequence carries the synchronization needed before reading the buffer. section 5.3.4

Listing 5.11. Reading values from a queue with atomic operations
#include <atomic>
#include <thread>
std::vector<int> queue_data;
std::atomic<int> count;
void populate_queue()
{
    unsigned const number_of_items=20;
    queue_data.clear();
    for(unsigned i=0;i<number_of_items;++i)
    {
        queue_data.push_back(i);
    }

    count.store(number_of_items,std::memory_order_release);               #1
}
void consume_queue_items()
{
    while(true)
    {
        int item_index;
        if((item_index=count.fetch_sub(1,std::memory_order_acquire))<=0)  #2
        {
            wait_for_more_items();                                        #3
            continue;
        }
        process(queue_data[item_index-1]);                                #4
    }
}
int main()
{
    std::thread a(populate_queue);
    std::thread b(consume_queue_items);
    std::thread c(consume_queue_items);
    a.join();
    b.join();
    c.join();
}

The listing shows the producer publishing 20 queued items and two consumers claiming them with fetch_sub; consumers wait when the count indicates no items remain. section 5.3.4

Release sequences also support synchronization in a lock-free stack: read-modify-write compare-and-exchange operations on the head can carry synchronization from a release compare-and-exchange in push to an acquire operation that reads its value. section 7.2.5

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

How deep it goes
Defines qualifying operations and memory-order conditions, then applies them to queue and lock-free stack examples.
Examples
Uses a shared queue count and lock-free stack head operations to demonstrate release sequences.
What you can do after
Understand when intervening atomic read-modify-write operations preserve synchronization from a release operation.
How it connects
Connects atomic read-modify-write operations with release-acquire synchronization and happens-before ordering.

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.