memory_order_consume

Contents[Show]

std::memory_order_consume is the most legendary of the six memory models. That's for two reasons. On one hand, std::memory_order_consume is extremely hard to get. On the other hand - that may change in the future - no compiler supports it.

 

How can it happen, that a compiler supports the C++11 standard, but doesn't support the memory model std::memory_order_consume? The answer is, that compiler maps std::memory_order_consume to std::memory_order_acquire. That is fine because both are load or acquire operations. std::memory_order_consume requires weaker synchronisation and ordering constraints. So the release-acquire ordering is potentially slower than the release-consume ordering but - that is the key point - well defined. 

To get an understanding of the release-consume ordering, it's a good idea to compare it with the release-acquire ordering. I speak in the post explicitly from the release-acquire ordering and not from the acquire-release semantic to emphasise the strong relationship of std::memory_order_consume and std::memory_order_acquire.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Stay informed about my mentoring programs.

 

 

Subscribe via E-Mail.

Release-acquire ordering

As a starting point, I use a program with two threads t1 and t2. t1 plays the role of the producer, t2 the role of the consumer. The atomic variable ptr helps to synchronize the producer and consumer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// acquireRelease.cpp

#include <atomic>
#include <thread>
#include <iostream>
#include <string>
 
std::atomic<std::string*> ptr;
int data;
std::atomic<int> atoData;
 
void producer(){
    std::string* p  = new std::string("C++11");
    data = 2011;
    atoData.store(2014,std::memory_order_relaxed);
    ptr.store(p, std::memory_order_release);
}
 
void consumer(){
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_acquire)));
    std::cout << "*p2: " << *p2 << std::endl;
    std::cout << "data: " << data << std::endl;
    std::cout << "atoData: " << atoData.load(std::memory_order_relaxed) << std::endl;
}
 
int main(){
    
    std::cout << std::endl;
    
    std::thread t1(producer);
    std::thread t2(consumer);
    
    t1.join();
    t2.join();
    
    std::cout << std::endl;
    
}

 

Before I analyse the program, I want to introduce a small variation. I replace in line 21 the memory model std::memory_order_acquire by std::memory_order_consume.

Release-consume ordering

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// acquireConsume.cpp

#include <atomic>
#include <thread>
#include <iostream>
#include <string>
 
std::atomic<std::string*> ptr;
int data;
std::atomic<int> atoData;
 
void producer(){
    std::string* p  = new std::string("C++11");
    data = 2011;
    atoData.store(2014,std::memory_order_relaxed);
    ptr.store(p, std::memory_order_release);
}
 
void consumer(){
    std::string* p2;
    while (!(p2 = ptr.load(std::memory_order_consume)));
    std::cout << "*p2: " << *p2 << std::endl;
    std::cout << "data: " << data << std::endl;
    std::cout << "atoData: " << atoData.load(std::memory_order_relaxed) << std::endl;
}
 
int main(){
    
    std::cout << std::endl;
    
    std::thread t1(producer);
    std::thread t2(consumer);
    
    t1.join();
    t2.join();
    
    std::cout << std::endl;
    
}

 

That was easy. But now the program has undefined behaviour. That statement is very hypothetical because my compiler implements std::memory_order_consume by std::memory_order_acquire. So under the hood, both program actually does the same.

Release-acquire versus Release-consume ordering

The output of the programs is identical.

 acquireReleaseConsume

Although I repeat myself, I want to sketch in a few words, why the first program acquireRelease.cpp is well defined.

The store operation in line 16 synchronizes-with the load operation in line 21. The reason is, that the store operation uses std::memory_order_release, that the load operation uses std::memory_order_acquire. That was the synchronization. What's about the ordering constraints of the release-acquire ordering? The release-acquire ordering guarantees, that all operations before the store operation (line 16) are available after the load operation (line 21). So the release-acquire operation orders in addition the access on the non-atomic variable data (line 14) and the atomic variable atoData (line 15). That holds although atoData uses the std::memory_order_relaxed memory model.

The key question is. What happens, if I replace the program std::memory_order_acquire by std::memory_order_consume?

Data dependencies with std::memory_order_consume

The std::memory_order_consume is about data dependencies on atomics. Data dependencies exist in two ways. At first carries-a-dependency-to in a thread and dependency-ordered_before between two threads. Both dependencies introduce a happens-before relation. That is this kind of relation a well-defined program needs.  But what means carries-a-dependency-to and dependency-order-before?

  • carries-a-dependency-to: If the result of an operation A is used as an operand of an operation B, then: A carries-a-dependency-to B.
  • dependency-ordered-before: A store operation (with std::memory_order_release, std::memory_order_acq_rel or std::memory_order_seq_cst), is dependency-ordered-before a load operation B (with std::memory_order_consume), if the result of the load operation B is used in a further operation C in the same thread. The operations B and C have to be in the same thread.

Of course, I know from personal experience, that both definitions are not easy to digest. So I will use a graphic to visually explain them.

dependency

The expression ptr.store(p, std::memory_order_release) is dependency-ordered-before while (!(p2 = ptr.load(std::memory_order_consume))), because in the following line std::cout << "*p2: " << *p2 << std::endl the result of the load operation will be read. Further, holds: while (!(p2 = ptr.load(std::memory_order_consume)) carries-a-dependency-to std::cout << "*p2: " << *p2 << std::endl, because the output of *p2 uses the result of the ptr.load operation.

But we have no guarantee for the following outputs of data and atoData. That's because both have no carries-a-dependency relation to the ptr.load operation. But it gets even worse. Because data is a non-atomic variable, there is a race condition on data. The reason is, that both threads can access data at the same time and thread t1 wants to modify data. Therefore, the program is undefined. 

What's next?

I admit that was a challenging post. In the next post, I deal with the typical misunderstanding of the acquire-release semantic. That happens, if the acquire operation is performed before the release operation.

 

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, and Ann Shatoff.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

Comments   

0 #1 Michael 2016-08-28 01:44
Greetings! Very useful advice within this article!

It's the little changes that produce the greatest changes.
Many thanks for sharing!
Quote
-2 #2 shopify themes 2016-12-22 15:35
If you want to grow your familiarity only keep visiting this web page and be updated with the hottest information posted here.
Quote
0 #3 Jatin Bhateja 2017-07-20 15:45
Hi Ranier, Intel Inspector 2018 beta shows race for above example having memory_order_acquire. Race is shown for
'data' which is written in producer and read inside consumer. I can email you the screen shot if you want. Program was compled with -pthread -g -std=c++14 . Complier version

PROMPT>g++.exe --version
g++.exe (i686-posix-dwarf-rev0, Built by MinGW-W64 project) 7.1.0
Quote

Mentoring

Stay Informed about my Mentoring

 

English Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

All tags

Blog archive

Source Code

Visitors

Today 3084

Yesterday 5317

Week 3084

Month 147255

All 11628409

Currently are 249 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments