大多数答案都正确地解决了您将遇到的 CPU 内存排序问题,但没有一个详细说明编译器如何通过以打破您假设的方式重新排序您的代码来挫败您的意图。
考虑取自这篇文章的一个例子:
volatile int ready;
int message[100];
void foo(int i)
{
message[i/10] = 42;
ready = 1;
}
在-O2
及以上,最新版本的 GCC 和 Intel C/C++(不了解 VC++)将ready
首先进行存储,因此它可以与计算重叠i/10
(volatile
不救你!):
leaq _message(%rip), %rax
movl $1, _ready(%rip) ; <-- whoa Nelly!
movq %rsp, %rbp
sarl $2, %edx
subl %edi, %edx
movslq %edx,%rdx
movl $42, (%rax,%rdx,4)
这不是错误,它是利用 CPU 流水线的优化器。如果另一个线程ready
在访问其内容之前正在等待,message
那么您将面临一场令人讨厌和晦涩的比赛。
使用编译器屏障以确保您的意图得到尊重。一个利用 x86 相对强排序的示例是发布/使用包装器,在 Dmitriy Vyukov 的 Single-Producer Single-Consumer 队列中发布:
// load with 'consume' (data-dependent) memory ordering
// NOTE: x86 specific, other platforms may need additional memory barriers
template<typename T>
T load_consume(T const* addr)
{
T v = *const_cast<T const volatile*>(addr);
__asm__ __volatile__ ("" ::: "memory"); // compiler barrier
return v;
}
// store with 'release' memory ordering
// NOTE: x86 specific, other platforms may need additional memory barriers
template<typename T>
void store_release(T* addr, T v)
{
__asm__ __volatile__ ("" ::: "memory"); // compiler barrier
*const_cast<T volatile*>(addr) = v;
}
我建议,如果您打算冒险进入并发内存访问领域,请使用一个可以为您处理这些细节的库。当我们都在等待n2145并std::atomic
查看 Thread Building Blockstbb::atomic
或即将推出的boost::atomic
.
除了正确性之外,这些库还可以简化您的代码并阐明您的意图:
// thread 1
std::atomic<int> foo; // or tbb::atomic, boost::atomic, etc
foo.store(1, std::memory_order_release);
// thread 2
int tmp = foo.load(std::memory_order_acquire);
使用显式内存排序,foo
的线程间关系清晰。