了解c ++ 11内存栅栏 [英] Understanding c++11 memory fences

查看:455
本文介绍了了解c ++ 11内存栅栏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图理解c ++ 11中的内存栅栏,我知道有更好的方法来做这个,原子变量等等,但想知道这种用法是否正确。我意识到这个程序没有什么有用的,我只是想确保使用fence函数做了我认为他们做了。

I'm trying to understand memory fences in c++11, I know there are better ways to do this, atomic variables and so on, but wondered if this usage was correct. I realize that this program doesn't do anything useful, I just wanted to make sure that the usage of the fence functions did what I thought they did.

基本上释放确保在该围栏之前对该线程所做的任何更改在该围栏之后对其他线程可见,并且在第二线程中,对该变量的任何更改在该围栏之后立即可见。

Basically that the release ensures that any changes made in this thread before the fence are visible to other threads after the fence, and that in the second thread that any changes to the variables are visible in the thread immediately after the fence?

我的理解是否正确?或者我完全错过了这一点?

Is my understanding correct? Or have I missed the point entirely?

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


推荐答案

不是实际上确保您在评论中提及的内容。也就是说,您使用栅栏并不能确保您对 a 的分配对其他线程可见,或者您从 a 是最新的。这是因为,虽然你似乎有使用栅栏的基本概念,你的代码实际上不满足那些栅栏同步的确切要求。

Your usage does not actually ensure the things you mention in your comments. That is, your usage of fences does not ensure that your assignments to a are visible to other threads or that the value you read from a is 'up to date.' This is because, although you seem to have the basic idea of where fences should be used, your code does not actually meet the exact requirements for those fences to "synchronize".

这是一个不同的例子,我认为演示正确的用法更好。

Here's a different example that I think demonstrates correct usage better.

#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

The load and store on the atomic flag do not synchronize, because they both use the relaxed memory ordering. Without the fences this code would be a data race, because we're performing conflicting operations a non-atomic object in different threads, and without the fences and the synchronization they provide there would be no happens-before relationship between the conflicting operations on a.

然而,对于栅栏,我们得到同步,因为我们保证线程2将读取标记通过线程1(因为我们循环,直到我们看到该值),并且由于原子写发生在释放栅栏和原子读取发生之后,在获取栅栏之前,栅栏同步。 (具体要求参见§29.8 / 2。)

However with the fences we do get synchronization because we've guaranteed that thread 2 will read the flag written by thread 1 (because we loop until we see that value), and since the atomic write happened after the release fence and the atomic read happens-before the acquire fence, the fences synchronize. (see § 29.8/2 for the specific requirements.)

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

This synchronization means anything that happens-before the release fence happens-before anything that happens-after the acquire fence. Therefore the non-atomic write to a happens-before the non-atomic read of a.

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

Things get trickier when you're writing a variable in a loop, because you might establish a happens-before relation for some particular iteration, but not other iterations, causing a data race.

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 = 1 a = 2 ,... a = 10 已经发生在 cout < ,但我们不会知道 cout 发生在 = 11 。这些是在不同线程上的冲突操作,没有发生之前的关系;数据竞赛。

This code still causes the fences to synchronize but does not eliminate data races. For example if f.load() happens to return 10 then we know that a=1,a=2, ... a=10 have all happened-before that particular cout<<a, but we don't know that cout<<a happens-before a=11. Those are conflicting operations on different threads with no happens-before relation; a data race.

这篇关于了解c ++ 11内存栅栏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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