首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >了解c++11内存屏障

了解c++11内存屏障
EN

Stack Overflow用户
提问于 2012-11-30 02:31:03
回答 2查看 29.2K关注 0票数 41

我正在尝试理解c++11中的内存栅栏,我知道有更好的方法来做到这一点,原子变量等等,但我想知道这种用法是否正确。我意识到这个程序并不能做任何有用的事情,我只是想确认一下如何使用围栏函数来实现我所认为的功能。

基本上,释放确保了在围栏之前的线程中所做的任何更改对于围栏之后的其他线程都是可见的,并且在第二个线程中,对变量的任何更改在围栏之后的线程中都是可见的?

我的理解正确吗?或者,我完全没有抓住要点?

代码语言:javascript
复制
#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();
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-11-30 03:38:04

你的使用实际上并不能确保你在评论中提到的事情。也就是说,您对a的使用并不能确保您对a的赋值对其他线程可见,也不能确保您从a读取的值是“最新的”。这是因为,尽管您似乎对应该在何处使用栅栏有了基本的概念,但您的代码实际上并不满足这些栅栏“同步”的确切要求。

这里有一个不同的例子,我认为它更好地演示了正确的用法。

代码语言:javascript
复制
#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写入的标志(因为我们循环直到看到那个值),而且由于原子写入发生在释放栅栏之后,原子读取发生在获取栅栏之前,栅栏同步。(具体要求见§29.8/2。)

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

当您在循环中编写变量时,事情会变得更加棘手,因为您可能会为某些特定迭代建立一个发生之前的关系,但不会为其他迭代建立关系,从而导致数据竞争。

代码语言:javascript
复制
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之前。这些是不同线程上的冲突操作,没有发生之前的关系;数据竞争。

票数 44
EN

Stack Overflow用户

发布于 2012-11-30 02:43:13

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

例如,编译器可以自由地在内部实现如下所示的a = i;

代码语言:javascript
复制
 while(a != i)
 {
    ++a;
    atomic_thread_fence(std::memory_order_release);
 }

所以另一个线程可能会看到任何值。

当然,编译器永远不会实现这样的简单赋值。然而,在某些情况下,类似的令人困惑的行为实际上是一种优化,因此依赖于以任何特定方式在内部实现的普通代码是一个非常糟糕的想法。这就是为什么我们有像原子操作这样的东西,而栅栏只有在与这些操作一起使用时才能产生有保证的结果。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13632344

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档