std::memory_order
当涉及到三个线程时,这里有一个关于 C++11 中规则的问题。比如说,一个线程生产者保存了一个值并设置了一个标志。然后,另一个线程中继在设置另一个标志之前等待这个标志。最后,第三个线程consumer等待来自relay的标志,这应该表示data
已为consumer准备好。
这是一个最小程序,采用 C++ 参考 ( 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))
;
// Does the following line make a 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();
}
注释:
第一个功能
relay_1()
不足,可以触发assert
in consumer。根据上面引用的 C++ 参考,memory_order_acquire
关键字“确保在其他线程中释放相同原子变量的所有写入在当前线程中都是可见的”。因此,设置 时对中继data=42
是可见的。它将它设置为“确保当前线程中的所有写入在获取相同原子变量的其他线程中可见”。但是,由于没有被relay触及,因此消费者可能会看到内存访问顺序不同,并且在消费者看到时可能未初始化。flag2
memory_order_release
data
data
flag2==True
相同的论点适用于
relay_2()
. 顺序一致的排序意味着“在所有标记为 的原子操作之间建立了同步std::memory_order_seq_cst
”。但是,这并没有说明变量data
。还是我在这里理解错误并且
relay_2()
足够了?让我们通过访问来解决这种
data
情况relay_3()
。在这里,该行data = data
表示在go todata
之后读取,中继线程在设置之前写入, 。因此,消费者线程必须看到正确的值。flag1
true
data
flag2
然而,这个解决方案似乎有点奇怪。该行
data = data
似乎编译器会(在顺序代码中)立即优化。虚拟线在这里起作用吗?
std::memory_order
使用 C++11特性实现跨三个线程同步的更好方法是什么?
顺便说一句,这不是一个学术问题。想象这data
是一个大数据块而不是单个整数,第i个线程需要将信息传递给第 ( i +1) 个线程,直到数据已被所有具有索引的线程处理的元素≤<em>i。
编辑:
阅读迈克尔伯尔的回答后,很明显这就relay_1()
足够了。请阅读他的帖子以获得完全令人满意的问题解决方案。C++11 标准提供了比仅从 cppreference.com 网站推断出的更严格的保证。因此,请认为 Michael Burr 帖子中的论点是权威的,而不是我上面的评论。要走的路是在所讨论的事件之间建立一个“线程间发生之前”的关系(它是可传递的)。