c++ - Synchronization riddle with std::memory_order and three threads -


here problem std::memory_order rules in c++11, when comes 3 threads. say, 1 thread producer saves value , sets flag. then, thread relay waits flag before setting flag. finally, third thread consumer waits flag relay, should signal data ready consumer.

here minimal program, in style of examples in c++ reference (http://en.cppreference.com/w/cpp/atomic/memory_order):

#include <thread> #include <atomic> #include <cassert>  std::atomic<bool> flag1 = atomic_var_init(false); std::atomic<bool> flag2 = atomic_var_init(false); int data;  void producer() {   data = 42;   flag1.store(true, std::memory_order_release); }  void relay_1() {   while (!flag1.load(std::memory_order_acquire))     ;   flag2.store(true, std::memory_order_release); }  void relay_2() {   while (!flag1.load(std::memory_order_seq_cst))     ;   flag2.store(true, std::memory_order_seq_cst); }  void relay_3() {   while (!flag1.load(std::memory_order_acquire))     ;   // following line make difference?   data = data;   flag2.store(true, std::memory_order_release); }  void consumer() {   while (!flag2.load(std::memory_order_acquire))     ;   assert(data==42); }  int main() {   std::thread a(producer);   std::thread b(relay_1);   std::thread c(consumer);   a.join(); b.join(); c.join(); } 

comments:

  1. the first function relay_1() insufficient , can trigger assert in consumer. according c++ reference cited above, memory_order_acquire keyword “ensures writes in other threads release same atomic variable visible in current thread”. so, data=42 visible relay when sets flag2. sets memory_order_release, “ensures writes in current thread visible in other threads acquire same atomic variable”. however, data has not been touched relay, consumer might see memory accesses in different order, , data may uninitialized when consumer sees flag2==true.

  2. the same argument applies stricter memory orderings in relay_2(). sequentially-consistent ordering implies “the synchronization established between atomic operations tagged std::memory_order_seq_cst”. however, doesn't variable data.

    or understand wrong here , relay_2() sufficient?

  3. let's resolve situation accesses data in relay_3(). here, line data = data implies data read after flag1 went true, , relay thread writes data, before sets flag2. hence, consumer thread must see correct value.

    however, solution seems little strange. line data = data seems compiler (in sequential code) optimize out.

    does dummy line trick here? better way achieve synchronization across 3 threads c++11 std::memory_order features?

by way, not academic question. imagine data big block of data instead of single integer, , i-th thread needs pass on information (i+1)-th thread element data has been processed threads index ≤i.

edit:

after reading michael burr's answer clear relay_1() sufficient. please read post satisfying solution problem. c++11 standard gives stricter guarantees can inferred cppreference.com web site alone. therefore, consider argumentation in michael burr's post authoritative, not comments above. way go establish “inter-thread happens-before” relation (which transitive) between events in question.

i think relay_1() sufficient pass value 42 producer consumer through data.

to show this, first i'll give single-letter names operations of interest:

void producer() {     /* p */ data = 42;     /* q */ flag1.store(true, std::memory_order_release); }  void relay_1() {   while (/* r */ !flag1.load(std::memory_order_acquire))     ;    /* s */ flag2.store(true, std::memory_order_release); }   void consumer() {   while (/* t */ !flag2.load(std::memory_order_acquire))     ;   /* u */ assert(data==42); } 

i'm going use notation a -> b mean "a inter-thread happens before b" (c++11 1.10/11).

i argue p visible side-effect respect u because:

  • p sequenced before q, r sequenced before s, , t sequenced before u (1.9/14)
  • q synchronizes r, , s synchronizes t (29.3/2)

all of next points supported definition of "inter-thread happens before" (1.10/11):

  • q -> s since standard says "a inter-thread happens before evaluation b if ... evaluation x, synchronizes x , x sequenced before b" (q synchronizes r , r sequenced before s, q -> s)

  • s -> u following similar logic (s synchronizes t , t sequenced before u, s -> u)

  • q -> u because q -> s , s -> u ("a inter-thread happens before evaluation b if ... inter-thread happens before x , x inter-thread happens before b")

and finally,

  • p -> u because p sequenced before q , q -> u ("a inter-thread happens before evaluation b if ... sequenced before x , x inter-thread happens before b")

since p inter-thread happens before u, p happens before u (1.10/12) , p "visible side-effect" respect u (1.10/13).

relay_3() sufficient because data=data expression irrelevant.

and purpose of producer/consumer problem, relay_2() @ least relay_1() because in store operation memory_order_seq_cst release , in load operation memory_order_seq_cst acquire (see 29.3/1). exact same logic can followed. operations using memory_order_seq_cst have additional properties having how memory_order_seq_cst sequenced among other memory_order_seq_cst operations, properties don't come play in example.

i think memory_order_acquire , memory_order_release not useful implementing higher level synchronization objects if there weren't transitive behavior such this.


Comments

Popular posts from this blog

c# - Send Image in Json : 400 Bad request -

jquery - Fancybox - apply a function to several elements -

An easy way to program an Android keyboard layout app -