在多生产者,多消费者的情况下。如果制作人正在写int a
,而消费者正在阅读int a
,我是否需要int a
周围的内存障碍?
我们都了解到:共享资源应该始终受到保护,标准不能保证正确的行为。
然而,在高速缓存一致的体系结构上,可以自动确保可见性,并保证8,16,32和64位变量MOV
操作的原子性。
因此,为什么要保护int a
?
答案 0 :(得分:5)
至少在C ++ 11(或更高版本)中,您不需要(明确地)使用互斥或内存障碍保护您的变量。
您可以使用std::atomic
创建原子变量。保证对该变量的更改将跨线程传播。
std::atomic<int> a;
// thread 1:
a = 1;
// thread 2 (later):
std::cout << a; // shows `a` has the value 1.
当然,除此之外还有更多 - 例如,不能保证std::cout
原子地工作,所以你可能必须保护它(如果你尝试的话)无论如何要从多个线程写入。
然后由编译器/标准库来确定处理原子性要求的最佳方法。在确保高速缓存一致性的典型体系结构中,它可能仅仅意味着不要在寄存器中分配此变量&#34;。它可能会造成内存障碍,但只有在真正需要它们的系统上才会这样做。
答案 1 :(得分:3)
然而,在缓存一致的体系结构上,可以自动确保可见性,并且保证8,16,32和64位变量的原子性MOV操作。
除非您严格遵守C ++规范的要求以避免数据争用,否则编译器没有义务按照它的方式使您的代码功能化。例如:
int a = 0, b = 0; // shared variables, initialized to zero
a = 1;
b = 1;
假设您在完全缓存一致的架构上执行此操作。在这样的硬件上,似乎是因为a是在b之前编写的,没有线程将能够看到值为1的b而没有具有该值。
但事实并非如此。如果您未能严格遵守C ++内存模型的要求以避免数据争用,例如你读取这些变量没有在任何地方插入正确的同步原语,那么你的程序实际上可能会观察b在a之前写入。原因是你引入了“未定义的行为”,C ++实现没有义务做任何对你有意义的事情。
实际上可能会发生的是,编译器可能会重新排序写入,即使硬件非常难以使其看起来好像所有写入都发生在执行写入的机器指令的顺序。您需要整个工具链进行合作,而仅仅硬件的合作(例如强缓存一致性)是不够的。
如果你想了解C ++内存模型的细节并用C ++编写可移植的并发代码,那么本书C++ Concurrency in Action就是一个很好的资源。