了解C ++ 11个存储器栅栏(Understanding c++11 memory fences)

2019-07-04 06:18发布

我想了解内存围栏在C ++ 11,我知道有更好的方法来做到这一点,原子变量等,但不知道是否这种用法是正确的。 我知道,这个方案没有做什么有用的,我只是想确保的栅栏函数的用法做了我认为他们做到了。

基本上,释放确保栅栏后的栅栏前,这个线程所做的任何更改对其他线程是可见的,并且在第二线程的变量的任何更改栅栏后的线程是立即可见?

我的理解是正确的吗? 还是我错过了点完全?

#include <iostream>
#include <atomic>
#include <thread>

int a;

void func1()
{
    for(int i = 0; i < 1000000; ++i)
    {
        a = i;
        // Ensure that changes to a to this point are visible to other threads
        atomic_thread_fence(std::memory_order_release);
    }
}

void func2()
{
    for(int i = 0; i < 1000000; ++i)
    {
        // Ensure that this thread's view of a is up to date
        atomic_thread_fence(std::memory_order_acquire);
        std::cout << a;
    }
}

int main()
{
    std::thread t1 (func1);
    std::thread t2 (func2);

    t1.join(); t2.join();
}

Answer 1:

您的使用实际上保证在您的评论提及的事情。 也就是说,你的栅栏的使用并不能保证你的任务到a可见于其他线程或从读取的值a是“最新”。 这是因为,尽管你似乎有哪里围栏,应使用你的代码实际上并不符合那些围栏“同步”具体要求的基本思想。

这里有一个不同的例子,我想演示正确使用好。

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<bool> flag(false);
int a;

void func1()
{
    a = 100;
    atomic_thread_fence(std::memory_order_release);
    flag.store(true, std::memory_order_relaxed);
}

void func2()
{
    while(!flag.load(std::memory_order_relaxed))
        ;

    atomic_thread_fence(std::memory_order_acquire);
    std::cout << a << '\n'; // guaranteed to print 100
}

int main()
{
    std::thread t1 (func1);
    std::thread t2 (func2);

    t1.join(); t2.join();
}

在原子标志的加载和存储不同步,因为它们都使用松散内存排序。 没有围墙的代码将是一个数据的比赛,因为我们正在执行冲突操作在不同的线程非原子对象,没有围墙,他们提供有会是没有同步之前发生的冲突的业务之间的关系a

然而,随着我们得到同步的,因为我们已经保证线程2将读取线程1所写的标志围栏(因为我们循环,直到我们看到值),并自发布后篱笆的原子写发生和原子发生读-before的获取围栏,围栏同步。 (见第对于具体要求29.8 / 2)。

这意味着同步发生的任何事情,之前释放栅栏之前发生任何情况,之后的获取栅栏。 因此,在非原子写a之前发生的非原子读a

事情变得棘手,当你在一个循环中写的变量,因为你可能建立之前发生的一些特定的迭代关系,而不是其他的迭代,造成数据竞争。

std::atomic<int> f(0);
int a;

void func1()
{
    for (int i = 0; i<1000000; ++i) {
        a = i;
        atomic_thread_fence(std::memory_order_release);
        f.store(i, std::memory_order_relaxed);
    }
}

void func2()
{
    int prev_value = 0;
    while (prev_value < 1000000) {
        while (true) {
            int new_val = f.load(std::memory_order_relaxed);
            if (prev_val < new_val) {
                prev_val = new_val;
                break;
            }
        }

        atomic_thread_fence(std::memory_order_acquire);
        std::cout << a << '\n';
    }
}

此代码还是引起了栅栏同步,但并不能消除数据争。 例如,如果f.load()恰好返回10后来我们知道, a=1a=2 ,... a=10都之前发生的那个特定cout<<a ,但我们知道cout<<a之前发生a=11 。 这些都在不同的线程冲突操作,没有之前发生关系; 数据竞争。



Answer 2:

您的使用是正确的,但不足以保证任何有用的东西。

例如,编译器可以自由地在内部执行a = i; 这样,如果它想:

 while(a != i)
 {
    ++a;
    atomic_thread_fence(std::memory_order_release);
 }

因此,其他线程可以一直看到任何价值。

当然,编译器将永远不会实现一个简单的分配这样。 不过,也有类似的地方令人费解的行为实际上是一种优化的情况下,所以这是一个非常糟糕的主意依靠普通的代码以任何具体的方式在内部实现。 这就是为什么我们有事情像原子操作和围栏只有这样的操作使用时,产生的结果保证。



文章来源: Understanding c++11 memory fences
标签: c++ c++11 atomic