Last active
March 2, 2025 07:26
-
-
Save vladiant/ea229a65d2e14a7a11242ad4ada10d5c to your computer and use it in GitHub Desktop.
C++ Threads Memory Order
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> | |
#include <string> | |
#include <thread> | |
#include <iostream> | |
// Global | |
std::string computation(int a){ | |
return std::to_string(a); | |
} | |
void print(const std::string& val){ | |
std::cout << val << '\n'; | |
} | |
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]); | |
} | |
int main() | |
{ | |
std::thread a(ThreadA, 1 ,2 ,3); | |
std::thread b(ThreadB); | |
std::thread c(ThreadB); | |
std::thread d(ThreadB); | |
a.join(); b.join(); | |
c.join(); d.join(); | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> | |
#include <iostream> | |
#include <thread> | |
#include <vector> | |
std::atomic<int> cnt = {0}; | |
void f() | |
{ | |
for (int n = 0; n < 1000; ++n) | |
cnt.fetch_add(1, std::memory_order_relaxed); | |
} | |
int main() | |
{ | |
std::vector<std::thread> v; | |
for (int n = 0; n < 10; ++n) | |
v.emplace_back(f); | |
for (auto& t : v) | |
t.join(); | |
std::cout << "Final counter value is " << cnt << '\n'; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> | |
#include <cassert> | |
#include <string> | |
#include <thread> | |
std::atomic<std::string*> ptr; | |
int data; | |
void producer() | |
{ | |
std::string* p = new std::string("Hello"); | |
data = 42; | |
ptr.store(p, std::memory_order_release); | |
} | |
void consumer() | |
{ | |
std::string* p2; | |
while (!(p2 = ptr.load(std::memory_order_acquire))) | |
; | |
assert(*p2 == "Hello"); // never fires | |
assert(data == 42); // never fires | |
} | |
int main() | |
{ | |
std::thread t1(producer); | |
std::thread t2(consumer); | |
t1.join(); t2.join(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> | |
#include <cassert> | |
#include <string> | |
#include <thread> | |
std::atomic<std::string*> ptr; | |
int data; | |
void producer() | |
{ | |
std::string* p = new std::string("Hello"); | |
data = 42; | |
ptr.store(p, std::memory_order_release); | |
} | |
void consumer() | |
{ | |
std::string* p2; | |
while (!(p2 = ptr.load(std::memory_order_acquire))) | |
; | |
assert(*p2 == "Hello"); // never fires | |
assert(data == 42); // never fires | |
} | |
int main() | |
{ | |
std::thread t1(producer); | |
std::thread t2(consumer); | |
t1.join(); t2.join(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> | |
#include <cassert> | |
#include <string> | |
#include <thread> | |
std::atomic<std::string*> ptr; | |
int data; | |
void producer() | |
{ | |
std::string* p = new std::string("Hello"); | |
data = 42; | |
ptr.store(p, std::memory_order_release); | |
} | |
void consumer() | |
{ | |
std::string* p2; | |
while (!(p2 = ptr.load(std::memory_order_consume))) | |
; | |
assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr | |
assert(data == 42); // may or may not fire: data does not carry dependency from ptr | |
} | |
int main() | |
{ | |
std::thread t1(producer); | |
std::thread t2(consumer); | |
t1.join(); t2.join(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> | |
#include <cassert> | |
#include <string> | |
#include <thread> | |
std::atomic<std::string*> ptr; | |
int data; | |
void producer() | |
{ | |
std::string* p = new std::string("Hello"); | |
data = 42; | |
ptr.store(p, std::memory_order_release); | |
} | |
void consumer() | |
{ | |
std::string* p2; | |
while (!(p2 = ptr.load(std::memory_order_consume))) | |
; | |
assert(*p2 == "Hello"); // never fires: *p2 carries dependency from ptr | |
assert(data == 42); // may or may not fire: data does not carry dependency from ptr | |
} | |
int main() | |
{ | |
std::thread t1(producer); | |
std::thread t2(consumer); | |
t1.join(); t2.join(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/questions/14581090/how-to-correctly-use-stdatomic-signal-fence | |
#include <atomic> | |
#include <csignal> | |
#include <cassert> | |
#include <cstdlib> | |
// static_assert(2 == ATOMIC_INT_LOCK_FREE, "this implementation does not guarantee that std::atomic<int> is always lock free."); | |
static_assert(std::atomic<int>::is_always_lock_free, "this implementation does not guarantee that std::atomic<int> is always lock free."); | |
std::atomic<int> a = 0; | |
std::atomic<int> b = 0; | |
extern "C" void handler(int) { | |
if (1 == a.load(std::memory_order_relaxed)) { | |
std::atomic_signal_fence(std::memory_order_acquire); | |
assert(1 == b.load(std::memory_order_relaxed)); | |
} | |
std::exit(0); | |
} | |
int main() { | |
std::signal(SIGTERM, &handler); | |
b.store(1, std::memory_order_relaxed); | |
std::atomic_signal_fence(std::memory_order_release); | |
a.store(1, std::memory_order_relaxed); | |
return 0; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/questions/13632344/understanding-c11-memory-fences | |
#include <iostream> | |
#include <atomic> | |
#include <thread> | |
std::atomic<bool> flag(false); | |
int a; | |
void func1() | |
{ | |
a = 100; | |
atomic_thread_fence(std::memory_order_release); | |
flag.store(true, std::memory_order_relaxed); | |
} | |
void func2() | |
{ | |
while(!flag.load(std::memory_order_relaxed)) | |
; | |
atomic_thread_fence(std::memory_order_acquire); | |
std::cout << a << '\n'; // guaranteed to print 100 | |
} | |
int main() | |
{ | |
std::thread t1 (func1); | |
std::thread t2 (func2); | |
t1.join(); t2.join(); | |
} | |
// Better | |
#include <iostream> | |
#include <atomic> | |
#include <thread> | |
std::atomic<bool> flag(false); | |
int a; | |
void func1() | |
{ | |
a = 100; | |
atomic_thread_fence(std::memory_order_release); | |
flag.store(true, std::memory_order_release); | |
} | |
void func2() | |
{ | |
while(!flag.load(std::memory_order_acquire)) | |
; | |
atomic_thread_fence(std::memory_order_acquire); | |
std::cout << a << '\n'; // guaranteed to print 100 | |
} | |
int main() | |
{ | |
std::thread t1 (func1); | |
std::thread t2 (func2); | |
t1.join(); t2.join(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://lemire.me/blog/2025/02/07/thread-safe-memory-copy/ | |
void safe_memcpy(char *dest, const char *src, size_t count) { | |
for (size_t i = 0; i < count; ++i) { | |
char input = | |
std::atomic_ref<const char>(src[i]) | |
.load(std::memory_order_relaxed); | |
std::atomic_ref<char>(dest[i]) | |
.store(input, std::memory_order_relaxed); | |
} | |
} | |
static_assert(std::atomic_ref<char>::required_alignment == 1); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment