假设目标平台上对齐的指针加载和存储自然是原子性的,那么这两者之间有什么区别:
// Case 1: Dumb pointer, manual fence
int* ptr;
// ...
std::atomic_thread_fence(std::memory_order_release);
ptr = new int(-4);
// Case 2: atomic var, automatic fence
std::atomic<int*> ptr;
// ...
ptr.store(new int(-4), std::memory_order_release);
还有这个:
// Case 3: atomic var, manual fence
std::atomic<int*> ptr;
// ...
std::atomic_thread_fence(std::memory_order_release);
ptr.store(new int(-4), std::memory_order_relaxed);
我原本认为它们都是等价的,但是Relacy只在第一种情况下检测到数据竞争:
struct test_relacy_behaviour : public rl::test_suite<test_relacy_behaviour, 2>
{
rl::var<std::string*> ptr;
rl::var<int> data;
void before()
{
ptr($) = nullptr;
rl::atomic_thread_fence(rl::memory_order_seq_cst);
}
void thread(unsigned int id)
{
if (id == 0) {
std::string* p = new std::string("Hello");
data($) = 42;
rl::atomic_thread_fence(rl::memory_order_release);
ptr($) = p;
}
else {
std::string* p2 = ptr($); // <-- Test fails here after the first thread completely finishes executing (no contention)
rl::atomic_thread_fence(rl::memory_order_acquire);
RL_ASSERT(!p2 || *p2 == "Hello" && data($) == 42);
}
}
void after()
{
delete ptr($);
}
};
我联系了Relacy的作者,想知道这是否是预期行为,他说我的测试用例中确实存在数据竞争。 然而,我很难发现它在哪里;有人能指出这个竞争吗? 最重要的是,这三种情况之间有什么区别?
更新:我想到Relacy可能只是在抱怨跨线程访问变量的原子性(或者缺乏原子性)……毕竟,它不知道我只打算在具有自然对齐整数/指针访问的平台上使用此代码。
另一个更新:Jeff Preshing撰写了一篇精彩的博客文章解释显式屏障和内置屏障之间的区别(“屏障”与“操作”)。第二种和第三种情况显然不等同!(在某些微妙的情况下,无论如何都是如此。)
std::atomic
变量(和相同的手动栅栏)时成功。 - Cameron