Home
Forums
New posts
Search forums
Online Courses
2023 Rankings
2023 MFE Programs Rankings Methodology
Reviews
Latest reviews
Search resources
Tracker
What's new
New posts
New media
New media comments
New resources
New profile posts
Latest activity
Log in
Register
What's new
Search
Search
Search titles only
By:
New posts
Search forums
Menu
Log in
Register
Install the app
Install
C++ Programming for Financial Engineering
Highly recommended by thousands of MFE students. Covers essential C++ topics with applications to financial engineering.
Learn more
Join!
Python for Finance with Intro to Data Science
Gain practical understanding of Python to read, understand, and write professional Python code for your first day on the job.
Learn more
Join!
An Intuition-Based Options Primer for FE
Ideal for entry level positions interviews and graduate studies, specializing in options trading arbitrage and options valuation models.
Learn more
Join!
Home
Forums
Quant discussion
Computing
Free course: C++ 11 to 20, std::atomic memory orders.
JavaScript is disabled. For a better experience, please enable JavaScript in your browser before proceeding.
You are using an out of date browser. It may not display this or other websites correctly.
You should upgrade or use an
alternative browser
.
Reply to thread
Message
<blockquote data-quote="ptf" data-source="post: 292327" data-attributes="member: 45662"><p>In this video, let's compare the different kinds of atomic's memory orders: relaxed, consume, acquire, release, sequentially consistent, and understand their differences and when to use them.</p><p></p><p>[MEDIA=youtube]UzYVDki31Hg[/MEDIA]</p><p></p><p>[CODE=cpp]// Video lecture: https://www.youtube.com/watch?v=UzYVDki31Hg</p><p></p><p>#include <iostream></p><p>#include <atomic></p><p>#include <thread></p><p>#include <random></p><p>#include <vector></p><p>#include <cassert></p><p>// #include "jthread.hpp"</p><p></p><p>using namespace std::chrono_literals;</p><p></p><p>void test_atomic_relaxed() {</p><p> // Atomic operations tagged memory_order_relaxed are not synchronization operations; they do not</p><p> // impose an order among concurrent memory accesses. They only guarantee atomicity and</p><p> // modification order consistency.</p><p> std::atomic<int> x = {0};</p><p> std::atomic<int> y = {0};</p><p> std::jthread t1([&]() {</p><p> auto r1 = y.load(std::memory_order_relaxed); // A</p><p> x.fetch_add(r1, std::memory_order_relaxed); // B</p><p> });</p><p></p><p> std::jthread t2([&]() {</p><p> auto r2 = x.load(std::memory_order_relaxed); // C</p><p> y.fetch_add(42, std::memory_order_relaxed); // D</p><p> });</p><p> // Possible outcomes</p><p> // CDAB: r1 = 42, r2 = 0</p><p> // ABCD: r1 = 0, r2 = 42</p><p> // DABC: r1 = 42, r2 = 42</p><p> // ...</p><p>}</p><p></p><p>void test_consume_release() {</p><p> // If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B</p><p> // from the same variable that read the stored value is tagged memory_order_consume, all memory</p><p> // writes (non-atomic and relaxed atomic) that happened-before the atomic store from the point</p><p> // of view of thread A, become visible side-effects within those operations in thread B into</p><p> // which the load operation carries dependency, that is, once the atomic load is completed,</p><p> // those operators and functions in thread B that use the value obtained from the load are</p><p> // guaranteed to see what thread A wrote to memory.</p><p></p><p> // The synchronization is established only between the threads releasing and consuming the same</p><p> // atomic variable. Other threads can see different order of memory accesses than either or both</p><p> // of the synchronized threads.</p><p></p><p> std::atomic<std::string*> ptr{};</p><p> int data;</p><p> std::string* p{};</p><p> std::jthread producer([&]() {</p><p> p = new std::string("Hello");</p><p> data = 42;</p><p> ptr.store(p, std::memory_order_release);</p><p> });</p><p></p><p> std::jthread consumer([&]() {</p><p> std::string* p2{};</p><p> while (!(p2 = ptr.load(std::memory_order_consume)))</p><p> ;</p><p> assert(*p == "Hello"); // always true</p><p> assert(*p2 == "Hello"); // always true: *p2 carries dependency from ptr</p><p> assert(data == 42); // may and may not be true: data does not carry dependency from ptr</p><p> delete p2;</p><p> });</p><p>}</p><p></p><p>void test_acquire_release_1() {</p><p> // If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B</p><p> // from the same variable is tagged memory_order_acquire, all memory writes (non-atomic and</p><p> // relaxed atomic) that happened-before the atomic store from the point of view of thread A,</p><p> // become visible side-effects in thread B. That is, once the atomic load is completed, thread B</p><p> // is guaranteed to see everything thread A wrote to memory.</p><p></p><p> std::atomic<std::string*> ptr{};</p><p> int data;</p><p> std::string* p{};</p><p> std::jthread producer([&]() {</p><p> p = new std::string("Hello");</p><p> data = 42;</p><p> ptr.store(p, std::memory_order_release);</p><p> });</p><p></p><p> std::jthread consumer([&]() {</p><p> std::string* p2{};</p><p> while (!(p2 = ptr.load(std::memory_order_acquire)))</p><p> ;</p><p> assert(*p == "Hello"); // always true</p><p> assert(*p2 == "Hello"); // always true</p><p> assert(data == 42); // always true</p><p> delete p2;</p><p> });</p><p>}</p><p></p><p>void test_acquire_release_2() {</p><p> // If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B</p><p> // from the same variable is tagged memory_order_acquire, all memory writes (non-atomic and</p><p> // relaxed atomic) that happened-before the atomic store from the point of view of thread A,</p><p> // become visible side-effects in thread B. That is, once the atomic load is completed, thread B</p><p> // is guaranteed to see everything thread A wrote to memory.</p><p></p><p> std::vector<int> data;</p><p> std::atomic<int> flag = {0};</p><p> std::jthread producer([&]() {</p><p> data.push_back(42);</p><p> flag.store(1, std::memory_order_release);</p><p> });</p><p></p><p> std::jthread consumer([&]() {</p><p> int expected = 1;</p><p> // Compares the contents of the flag with expected:</p><p> // - if true, it replaces the flag value with 2. (performs read-modify-write operation)</p><p> // - if false, it replaces expected with the flag. (performs load operation)</p><p> // returns</p><p> // - true if expected compares equal to the contained value.</p><p> // - false otherwise.</p><p> while (!flag.compare_exchange_weak(expected, 2, std::memory_order_acq_rel)) {</p><p> expected = 1;</p><p> }</p><p> assert(data.at(0) == 42); // always true</p><p> });</p><p>}</p><p></p><p>void test_seq_cst() {</p><p> // A load operation with this memory order performs an acquire operation, a store performs a</p><p> // release operation, and read-modify-write performs both an acquire operation and a release</p><p> // operation, plus a single total order exists in which all threads observe all modifications in</p><p> // the same order</p><p></p><p> std::atomic<bool> x = {false};</p><p> std::atomic<bool> y = {false};</p><p> std::atomic<int> z = {0};</p><p></p><p> {</p><p> std::jthread write_x([&]() {</p><p> //</p><p> x.store(true, std::memory_order_seq_cst);</p><p> });</p><p> std::jthread write_y([&]() {</p><p> y.store(true, std::memory_order_seq_cst);</p><p> });</p><p> std::jthread read_x_then_y([&]() {</p><p> while (!x.load(std::memory_order_seq_cst))</p><p> ;</p><p> if (y.load(std::memory_order_seq_cst)) {</p><p> ++z;</p><p> }</p><p> });</p><p> std::jthread read_y_then_x([&]() {</p><p> while (!y.load(std::memory_order_seq_cst))</p><p> ;</p><p> if (x.load(std::memory_order_seq_cst)) {</p><p> ++z;</p><p> }</p><p> });</p><p> }</p><p> assert(z.load() != 0);</p><p>}</p><p></p><p>int main() {</p><p> test_atomic_relaxed();</p><p> test_consume_release();</p><p> test_acquire_release_1();</p><p> test_acquire_release_2();</p><p> test_seq_cst();</p><p> return 0;</p><p>}[/CODE]</p></blockquote><p></p>
[QUOTE="ptf, post: 292327, member: 45662"] In this video, let's compare the different kinds of atomic's memory orders: relaxed, consume, acquire, release, sequentially consistent, and understand their differences and when to use them. [MEDIA=youtube]UzYVDki31Hg[/MEDIA] [CODE=cpp]// Video lecture: https://www.youtube.com/watch?v=UzYVDki31Hg #include <iostream> #include <atomic> #include <thread> #include <random> #include <vector> #include <cassert> // #include "jthread.hpp" using namespace std::chrono_literals; void test_atomic_relaxed() { // Atomic operations tagged memory_order_relaxed are not synchronization operations; they do not // impose an order among concurrent memory accesses. They only guarantee atomicity and // modification order consistency. std::atomic<int> x = {0}; std::atomic<int> y = {0}; std::jthread t1([&]() { auto r1 = y.load(std::memory_order_relaxed); // A x.fetch_add(r1, std::memory_order_relaxed); // B }); std::jthread t2([&]() { auto r2 = x.load(std::memory_order_relaxed); // C y.fetch_add(42, std::memory_order_relaxed); // D }); // Possible outcomes // CDAB: r1 = 42, r2 = 0 // ABCD: r1 = 0, r2 = 42 // DABC: r1 = 42, r2 = 42 // ... } void test_consume_release() { // If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B // from the same variable that read the stored value is tagged memory_order_consume, all memory // writes (non-atomic and relaxed atomic) that happened-before the atomic store from the point // of view of thread A, become visible side-effects within those operations in thread B into // which the load operation carries dependency, that is, once the atomic load is completed, // those operators and functions in thread B that use the value obtained from the load are // guaranteed to see what thread A wrote to memory. // The synchronization is established only between the threads releasing and consuming the same // atomic variable. Other threads can see different order of memory accesses than either or both // of the synchronized threads. std::atomic<std::string*> ptr{}; int data; std::string* p{}; std::jthread producer([&]() { p = new std::string("Hello"); data = 42; ptr.store(p, std::memory_order_release); }); std::jthread consumer([&]() { std::string* p2{}; while (!(p2 = ptr.load(std::memory_order_consume))) ; assert(*p == "Hello"); // always true assert(*p2 == "Hello"); // always true: *p2 carries dependency from ptr assert(data == 42); // may and may not be true: data does not carry dependency from ptr delete p2; }); } void test_acquire_release_1() { // If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B // from the same variable is tagged memory_order_acquire, all memory writes (non-atomic and // relaxed atomic) that happened-before the atomic store from the point of view of thread A, // become visible side-effects in thread B. That is, once the atomic load is completed, thread B // is guaranteed to see everything thread A wrote to memory. std::atomic<std::string*> ptr{}; int data; std::string* p{}; std::jthread producer([&]() { p = new std::string("Hello"); data = 42; ptr.store(p, std::memory_order_release); }); std::jthread consumer([&]() { std::string* p2{}; while (!(p2 = ptr.load(std::memory_order_acquire))) ; assert(*p == "Hello"); // always true assert(*p2 == "Hello"); // always true assert(data == 42); // always true delete p2; }); } void test_acquire_release_2() { // If an atomic store in thread A is tagged memory_order_release and an atomic load in thread B // from the same variable is tagged memory_order_acquire, all memory writes (non-atomic and // relaxed atomic) that happened-before the atomic store from the point of view of thread A, // become visible side-effects in thread B. That is, once the atomic load is completed, thread B // is guaranteed to see everything thread A wrote to memory. std::vector<int> data; std::atomic<int> flag = {0}; std::jthread producer([&]() { data.push_back(42); flag.store(1, std::memory_order_release); }); std::jthread consumer([&]() { int expected = 1; // Compares the contents of the flag with expected: // - if true, it replaces the flag value with 2. (performs read-modify-write operation) // - if false, it replaces expected with the flag. (performs load operation) // returns // - true if expected compares equal to the contained value. // - false otherwise. while (!flag.compare_exchange_weak(expected, 2, std::memory_order_acq_rel)) { expected = 1; } assert(data.at(0) == 42); // always true }); } void test_seq_cst() { // A load operation with this memory order performs an acquire operation, a store performs a // release operation, and read-modify-write performs both an acquire operation and a release // operation, plus a single total order exists in which all threads observe all modifications in // the same order std::atomic<bool> x = {false}; std::atomic<bool> y = {false}; std::atomic<int> z = {0}; { std::jthread write_x([&]() { // x.store(true, std::memory_order_seq_cst); }); std::jthread write_y([&]() { y.store(true, std::memory_order_seq_cst); }); std::jthread read_x_then_y([&]() { while (!x.load(std::memory_order_seq_cst)) ; if (y.load(std::memory_order_seq_cst)) { ++z; } }); std::jthread read_y_then_x([&]() { while (!y.load(std::memory_order_seq_cst)) ; if (x.load(std::memory_order_seq_cst)) { ++z; } }); } assert(z.load() != 0); } int main() { test_atomic_relaxed(); test_consume_release(); test_acquire_release_1(); test_acquire_release_2(); test_seq_cst(); return 0; }[/CODE] [/QUOTE]
Verification
Post reply
Home
Forums
Quant discussion
Computing
Free course: C++ 11 to 20, std::atomic memory orders.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.
Accept
Learn more…
Top