0

I have a set of global values that are initialized at program start and never change during the run. Those values are read from multiple threads, and I want to make sure the initialization is done correctly for all threads.

In this example,

int A, B;

void init() {
  A = 1;
  B = 2;
}

void read(int *a, int *b) {
  *a = A;
  *b = B;
}

void f() {
  /* ... */
  read(&a, &b);
  /* ... */
}

int main() {
  init();
  startThread(f);
  /* ... */
}

My understanding is that the compiler isn't allowed to put init after startThread because the side effects of the functions should be sequenced as written, so the machine instructions to store values in A and B will come before the thread start. However, this doesn't guarantee that the load in read from a different thread will happen after the stores are complete because the stores might for some reason be deferred after the loads.

Here's an obvious fix.

void init() {
  A = 1;
  B = 2;
  atomic_thread_fence(memory_order_release);
}

void read(int *a, int *b) {
  atomic_thread_fence(memory_order_acquire);
  *a = A;
  *b = B;
}

It should work if I've read the specs correctly, but the problem is that now, all functions that read those global values need a fence at function entry, and I'd like to avoid this. Unfortunately, all defined memory orders including memory_order_seq_cst are defined for cases where the reader is an atomic operation or has a fence.

Under the strong memory model of x86, the example code will work without any fix. I'm looking for a portable solution, but I'm also wondering whether it really could break on a different platform without synchronization.

If synchronization is necessary, what is a good way in this case? Is it possible without being intrusive to all the reader functions?

xiver77
  • 2,162
  • 1
  • 2
  • 12
  • Normally you'd just statically initialize them, like `int A=1, B=2;` (then you can even use `const`.) I guess you mean run-invariant but not *statically* known at compile-time, like `int A=rand(), B=rand();` which is only legal in C++ (and either way involves run-time init) – Peter Cordes Jul 10 '22 at 10:02
  • @PeterCordes I am initializing them before starting any threads, also in the example. I wrote in the second paragraph why I think that might not work. – xiver77 Jul 10 '22 at 10:06
  • Also, no, `atomic_thread_fence(memory_order_release);` there doesn't do anything. There's no following atomic store to create acq/rel sync between threads based on the barrier. Just call `init` before starting any other threads; that's guaranteed to work just like in C++. IIRC, there's a happens-before relationship between code before a thread starts, wrt. code in that thread. I guess you were thinking you needed manual fences to create that sync, with thread-startup as the atomic operation. – Peter Cordes Jul 10 '22 at 10:07
  • (sorry for my c++ answer ... my eyes added `++` after the `c` :-) ) – Ted Lyngmo Jul 10 '22 at 10:08
  • For C++, see [Implicit synchronization when creating/joining threads](https://stackoverflow.com/q/29684369) – Peter Cordes Jul 10 '22 at 10:12

0 Answers0