C++并发:为何第二个消费者无法与populate_queue同步,需fetch_sub的release?
C++并发同步疑问解析
代码片段(来自《C++ Concurrency in Action》5.3.4节)
#include <atomic> #include <thread> std::vector<int> queue_data; std::atomic<int> count; void populate_queue() { unsigned const number_of_items=20; queue_data.clear(); for(unsigned i=0;i<number_of_items;++i) { queue_data.push_back(i); } count.store(number_of_items,std::memory_order_release); } void consume_queue_items() { while(true) { int item_index; if((item_index=count.fetch_sub(1,std::memory_order_acquire))<=0) { wait_for_more_items(); continue; } process(queue_data[item_index-1]); } } int main() { std::thread a(populate_queue); std::thread b(consume_queue_items); std::thread c(consume_queue_items); a.join(); b.join(); c.join(); }
原文引用
若无release序列规则或fetch_sub操作上的memory_order_release,将无法保证queue_data的存储对第二个消费者可见,会引发数据竞争。
疑问
为何需要fetch_sub的release语义来同步queue_data的存储?第二个消费者为什么不能直接和populate_queue线程的release操作同步?
解答
核心原因在于原子操作的同步关系是一对一的,而非一对多:
- 第一个消费者线程调用
fetch_sub(acquire)时,读取的是populate_queue中store(release)写入的原始值,二者直接形成同步关系,因此该线程能确保看到queue_data的所有前置写入操作。 - 但第二个消费者线程调用的
fetch_sub读取的是第一个消费者fetch_sub修改后的值,并非populate_queue写入的原始值。如果fetch_sub仅用acquire语义,且没有release序列规则兜底,那么第二个消费者的acquire操作和populate_queue的release操作之间没有合法的同步链路。
这里的关键是C++内存模型中的release序列规则:当原子变量上执行了release操作后,后续对该变量的一系列原子读-改-写(RMW)操作(比如fetch_sub)会被纳入release序列。此时,后续线程对该变量的acquire操作,就能通过这个序列间接和最初的release操作建立同步关系,从而确保能看到release操作之前的所有内存写入。
如果没有release序列,且fetch_sub不带release语义,第二个消费者拿到的是第一个线程修改后的count值,这个修改操作没有release语义来传递同步关系,自然无法保证它能看到queue_data的内容,最终导致数据竞争。
内容的提问来源于stack exchange,提问作者Rupa
相关产品推荐
相关产品推荐

