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 triggerassertin consumer. according c++ reference cited above,memory_order_acquirekeyword “ensures writes in other threads release same atomic variable visible in current thread”. so,data=42visible relay when setsflag2. setsmemory_order_release, “ensures writes in current thread visible in other threads acquire same atomic variable”. however,datahas not been touched relay, consumer might see memory accesses in different order, ,datamay 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
datainrelay_3(). here, linedata = dataimpliesdataread afterflag1wenttrue, , relay thread writesdata, before setsflag2. hence, consumer thread must see correct value.however, solution seems little strange. line
data = dataseems compiler (in sequential code) optimize out.does dummy line trick here? better way achieve synchronization across 3 threads c++11
std::memory_orderfeatures?
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:
psequenced beforeq,rsequenced befores, ,tsequenced beforeu(1.9/14)qsynchronizesr, ,ssynchronizest(29.3/2)
all of next points supported definition of "inter-thread happens before" (1.10/11):
q -> ssince standard says "a inter-thread happens before evaluation b if ... evaluation x, synchronizes x , x sequenced before b" (qsynchronizesr,rsequenced befores,q -> s)s -> ufollowing similar logic (ssynchronizest,tsequenced beforeu,s -> u)q -> ubecauseq -> 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 -> ubecausepsequenced 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