Skip to content

Instantly share code, notes, and snippets.

@vladiant
Last active March 2, 2025 07:26
Show Gist options
  • Save vladiant/ea229a65d2e14a7a11242ad4ada10d5c to your computer and use it in GitHub Desktop.
Save vladiant/ea229a65d2e14a7a11242ad4ada10d5c to your computer and use it in GitHub Desktop.
C++ Threads Memory Order
#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;
}
#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';
}
#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();
}
#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();
}
#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();
}
#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();
}
// 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;
}
// 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();
}
// 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