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:
the first function
relay_1()
insufficient , can triggerassert
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 setsflag2
. setsmemory_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 seesflag2==true
.the same argument applies stricter memory orderings in
relay_2()
. sequentially-consistent ordering implies “the synchronization established between atomic operations taggedstd::memory_order_seq_cst
”. however, doesn't variabledata
.or understand wrong here ,
relay_2()
sufficient?let's resolve situation accesses
data
inrelay_3()
. here, linedata = data
impliesdata
read afterflag1
wenttrue
, , relay thread writesdata
, before setsflag2
. 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 beforeq
,r
sequenced befores
, ,t
sequenced beforeu
(1.9/14)q
synchronizesr
, ,s
synchronizest
(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
synchronizesr
,r
sequenced befores
,q -> s
)s -> u
following similar logic (s
synchronizest
,t
sequenced beforeu
,s -> u
)q -> u
becauseq -> 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
becausep
sequenced beforeq
,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
Post a Comment