使用显式栅栏和std :: atomic之间有什么区别? [英] What is the difference between using explicit fences and std::atomic?

查看:138
本文介绍了使用显式栅栏和std :: atomic之间有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设对齐的指针加载和存储在目标平台上是自然原子的,这之间的区别是:

  / case 1:哑指针,手动fence 
int * ptr;
// ...
std :: atomic_thread_fence(std :: memory_order_release);
ptr = new int(-4);

这:

 code> // 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>数据;

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($); //< - 在第一个线程完成执行后,测试失败(无争用)
rl :: atomic_thread_fence(rl :: memory_order_acquire);

RL_ASSERT(!p2 || * p2 ==Hello&&& amp; data($)== 42);
}
}

void after()
{
delete ptr($);
}
};

我联系了Relacy的作者,了解这是否是预期的行为;他说在我的测试用例中确实存在数据竞争。
然而,我有麻烦找到它;有人可以指出我的比赛是什么?
最重要的是,这三种情况之间的区别是什么?



更新:我发现Relacy可能只是抱怨关于跨线程访问的变量的原子性(或缺少)...毕竟,它不知道我打算只使用这个代码

另一个更新:Jeff Preshing撰写了一个优秀的博客文章解释显式栅栏和内置栅栏之间的区别(fencesvsoperations)。情况2和3显然不等同! (在某些细微的情况下,无论如何。)

解决方案

我相信代码有一个比赛。

blockquote>

-2-如果存在原子操作 X ,则释放栅栏 A 与获取栅栏 ,在 X 之前对 进行排序, B 之前对 修改 Y 进行排序, > X 或假设释放序列中的任何副作用写入的值 将指向


在case 1中,你的释放栅栏不会与你的获取栅栏同步,因为 ptr 不是一个原子对象,



情况2和情况3是等价的,因为 ptr

code>是一个原子对象,存储是一个原子操作。 ([atomic.fences]的第3和第4段描述了栅栏如何与原子操作同步,反之亦然。)



栅栏的语义只定义原子对象和原子操作。无论您的目标平台和实现是否提供更强的保证(例如将任何指针类型视为原子对象)都是实现定义的。


Assuming that aligned pointer loads and stores are naturally atomic on the target platform, what is the difference between this:

// Case 1: Dumb pointer, manual fence
int* ptr;
// ...
std::atomic_thread_fence(std::memory_order_release);
ptr = new int(-4);

this:

// Case 2: atomic var, automatic fence
std::atomic<int*> ptr;
// ...
ptr.store(new int(-4), std::memory_order_release);

and this:

// 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);

I was under the impression that they were all equivalent, however Relacy detects a data race in the first case (only):

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($);
    }
};

I contacted the author of Relacy to find out if this was expected behaviour; he says that there is indeed a data race in my test case. However, I'm having trouble spotting it; can someone point out to me what the race is? Most importantly, what are the differences between these three cases?

Update: It's occurred to me that Relacy may simply be complaining about the atomicity (or lack thereof, rather) of the variable being accessed across threads... after all, it doesn't know that I intend only to use this code on platforms where aligned integer/pointer access is naturally atomic.

Another update: Jeff Preshing has written an excellent blog post explaining the difference between explicit fences and the built-in ones ("fences" vs "operations"). Cases 2 and 3 are apparently not equivalent! (In certain subtle circumstances, anyway.)

解决方案

I believe the code has a race. Case 1 and case 2 are not equivalent.

29.8 [atomics.fences]

-2- A release fence A synchronizes with an acquire fence B if there exist atomic operations X and Y, both operating on some atomic object M, such that A is sequenced before X, X modifies M, Y is sequenced before B, and Y reads the value written by X or a value written by any side effect in the hypothetical release sequence X would head if it were a release operation.

In case 1 your release fence does not synchronize with your acquire fence because ptr is not an atomic object and the store and load on ptr are not atomic operations.

Case 2 and case 3 are equivalent, because ptr is an atomic object and the store is an atomic operation. (Paragraphs 3 and 4 of [atomic.fences] describe how a fence synchronizes with an atomic operation and vice versa.)

The semantics of fences are defined only with respect to atomic objects and atomic operations. Whether your target platform and your implementation offer stronger guarantees (such as treating any pointer type as an atomic object) is implementation-defined at best.

这篇关于使用显式栅栏和std :: atomic之间有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆