标签: memory-barriers

函数调用是现代平台的有效内存障碍吗?

在我评论的代码库中,我发现了以下习语.

void notify(struct actor_t act) {
    write(act.pipe, "M", 1);
}
// thread A sending data to thread B
void send(byte *data) {
    global.data = data;
    notify(threadB);
}
// in thread B event loop
read(this.sock, &cmd, 1);
switch (cmd) {
    case 'M': use_data(global.data);break;
    ...
}
Run Code Online (Sandbox Code Playgroud)

"抓住它",我对作者说,我的团队的一名高级成员,"这里没有内存障碍!你不能保证global.data将从缓存刷新到主内存.如果线程A和线程B将运行两个不同的处理器 - 这个方案可能会失败".

高级程序员咧嘴一笑,慢慢解释,仿佛在解释他五岁的男孩如何系鞋带:"听小男孩,我们在这里看到很多线程相关的错误,在高负载测试中,在真实客户中",他他停下来留下他长长的胡须,"但我们从来没有这个成语的错误".

"但是,它在书中说......"

"安静!",他立刻叫醒我,"也许理论上,它不能保证,但实际上,你使用函数调用的事实实际上是一个内存屏障.编译器不会重新排序指令global.data = data,因为它无法知道是否任何人在函数调用中使用它,并且x86架构将确保其他CPU在线程B从管道读取命令时将看到这段全局数据.请放心,我们有充足的现实问题需要担心.我们不需要在虚假的理论问题上投入额外的努力.

"请放心,我的孩子,你会理解将真正的问题与我需要获得博士的非问题分开."

他是对的吗?这在实践中真的不是问题(比如x86,x64和ARM)吗?

这是我所学到的一切,但他确实有一个长胡子和一个非常聪明的外观!

额外的积分如果你能告诉我一段代码证明他错了!

c multithreading memory-barriers

66
推荐指数
2
解决办法
4757
查看次数

我如何理解读取内存障碍和易失性

某些语言提供的volatile修饰符被描述为在读取支持变量的内存之前执行"读取内存屏障".

读取存储器屏障通常被描述为一种方法,用于确保CPU在屏障之后执行读取之前执行读取之前所请求的读取.但是,使用此定义,似乎仍然可以读取过时值.换句话说,以特定顺序执行读取似乎并不意味着必须查询主存储器或其他CPU以确保读取的后续值实际上反映了读取屏障时系统中的最新值或随后写入阅读障碍.

因此,volatile是否真的保证读取最新值或者只是(喘气!)读取的值至少与屏障之前的读取一样是最新的?还是其他一些解释?这个答案有什么实际意义?

multithreading volatile memory-barriers

56
推荐指数
2
解决办法
2万
查看次数

为什么我们需要Thread.MemoryBarrier()?

在"C#4 in a Nutshell"中,作者表明这个类有时可以写0 MemoryBarrier但是我无法在我的Core2Duo中重现:

public class Foo
{
    int _answer;
    bool _complete;
    public void A()
    {
        _answer = 123;
        //Thread.MemoryBarrier();    // Barrier 1
        _complete = true;
        //Thread.MemoryBarrier();    // Barrier 2
    }
    public void B()
    {
        //Thread.MemoryBarrier();    // Barrier 3
        if (_complete)
        {
            //Thread.MemoryBarrier();       // Barrier 4
            Console.WriteLine(_answer);
        }
    }
}

private static void ThreadInverteOrdemComandos()
{
    Foo obj = new Foo();

    Task.Factory.StartNew(obj.A);
    Task.Factory.StartNew(obj.B);

    Thread.Sleep(10);
}
Run Code Online (Sandbox Code Playgroud)

这种需要对我来说似乎很疯狂.如何识别出现这种情况的所有可能情况?我认为如果处理器改变了操作顺序,它需要保证行为不会改变.

你还懒得使用障碍吗?

.net c# multithreading thread-safety memory-barriers

47
推荐指数
2
解决办法
2万
查看次数

在x86/x86_64处理器上使用LFENCE指令是否有意义?

通常在互联网上我发现LFENCE在处理器x86中没有任何意义,即它什么都不做,所以相反MFENCE我们可以绝对无痛地使用SFENCE,因为MFENCE= SFENCE+ LFENCE= SFENCE+ NOP= SFENCE.

但是如果LFENCE没有意义,那么为什么我们有四种方法在x86/x86_64中建立顺序一致性:

  1. LOAD(没有围栏)和STORE+MFENCE
  2. LOAD (没有围栏)和 LOCK XCHG
  3. MFENCE+ LOADSTORE(没有围栏)
  4. LOCK XADD(0)和STORE(没有围栏)

取自这里:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

以及Herb Sutter在第34页底部的表演:https://skydrive.live.com/view.aspx?status = 4E86B0CF20EF15AD!24884&app = WordPdf&wdo = 2&authkey =!AMtj_EflYn2507c

如果LFENCE没有做任何事情,那么方法(3)将具有以下含义:SFENCE + LOAD and STORE (without fence)但是SFENCE之前没有任何意义LOAD.即如果LFENCE什么都不做,方法(3)没有意义.

LFENCE在处理器x86/x86_64中是否有任何意义上的指令?

回答:

1. …

x86 assembly x86-64 atomic memory-barriers

40
推荐指数
2
解决办法
1万
查看次数

互斥锁功能是否足够而没有易失性?

我和同事为在x86,x64,Itanium,PowerPC和其他10年历史的服务器CPU上运行的各种平台编写软件.

我们刚刚讨论了pthread_mutex_lock()... pthread_mutex_unlock()等互斥函数本身是否足够,或者受保护变量是否需要是volatile.

int foo::bar()
{
 //...
 //code which may or may not access _protected.
 pthread_mutex_lock(m);
 int ret = _protected;
 pthread_mutex_unlock(m);
 return ret;
}
Run Code Online (Sandbox Code Playgroud)

我担心的是缓存.编译器是否可以在堆栈或寄存器中放置_protected的副本,并在赋值中使用该陈旧值?如果没有,是什么阻止了这种情况发生?这种模式的变化是否易受攻击?

我假设编译器实际上并不理解pthread_mutex_lock()是一个特殊函数,所以我们只是受序列点保护吗?

非常感谢.

更新:好的,我可以看到一个趋势,答案解释了为什么不稳定是坏的.我尊重这些答案,但有关该主题的文章很容易在网上找到.我在网上找不到的,以及我问这个问题的原因,就是我如何保护我没有不稳定.如果上面的代码是正确的,那么缓存问题如何无懈可击?

c++ multithreading mutex volatile memory-barriers

39
推荐指数
3
解决办法
7299
查看次数

为什么我需要内存屏障?

Nutshell中的C#4(强烈推荐的btw)使用以下代码来演示MemoryBarrier的概念(假设A和B在不同的线程上运行):

class Foo{
  int _answer;
  bool complete;
  void A(){
    _answer = 123;
    Thread.MemoryBarrier(); // Barrier 1
    _complete = true;
    Thread.MemoryBarrier(); // Barrier 2
  }
  void B(){
    Thread.MemoryBarrier(); // Barrier 3;
    if(_complete){
      Thread.MemoryBarrier(); // Barrier 4;
      Console.WriteLine(_answer);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

他们提到障碍1和4阻止这个例子写0和障碍2和3提供新鲜度保证:他们确保如果B在A之后运行,读_complete将评估为.

我不是真的得到它.我想我明白为什么壁垒1和4是必要的:我们不想在写_answer进行优化,并放置在写后_complete(屏障1),我们需要确保_answer没有被缓存(光栅4) .我也认为我理解为什么Barrier 3是必要的:如果A在写完_complete = true之后才运行,B仍然需要刷新_complete以读取正确的值.

我不明白为什么我们需要障碍2!我的一部分说这是因为可能线程2(运行B)已经运行直到(但不包括)if(_complete),因此我们需要确保_complete被刷新.

但是,我不知道这有多大帮助.是不是仍然可以在A 中将_complete设置为true但是B方法会看到_complete的缓存(错误)版本?即,如果线程2运行方法B直到第一个MemoryBarrier之后,然后线程1运行方法A直到_complete = true但没有进一步,然后线程1恢复并测试是否(_complete) - 如果不导致错误 …

c# multithreading thread-safety shared-memory memory-barriers

34
推荐指数
2
解决办法
9488
查看次数

何时只有编译器的内存屏障(例如std :: atomic_signal_fence)有用吗?

当我正在阅读有关内存模型,障碍,排序,原子等等时,编译器栅栏的概念经常会出现,但通常情况下,它也会CPU栅栏配对,正如人们所期望的那样.

但是,偶尔我会读到适用于编译器的fence构造.一个例子是C++ 11 std::atomic_signal_fence函数,它在cppreference.com上声明:

std :: atomic_signal_fence等效于std :: atomic_thread_fence,除了没有发出内存排序的CPU指令.仅按顺序指示抑制编译器对指令的重新排序.

我有五个与此主题相关的问题:

  1. 正如名称所暗示的那样std::atomic_signal_fence,是一个异步中断(例如一个被内核抢占以执行信号处理程序的线程)唯一一种只有编译器的栅栏才有用的情况?

  2. 它的用处是否适用于所有体系结构,包括强烈排序的体系结构x86

  3. 是否可以提供一个特定的示例来演示仅编译器栅栏的用途?

  4. 使用时std::atomic_signal_fence,使用acq_relseq_cst订购之间有什么区别吗?(我希望它没有任何区别.)

  5. 这个问题可能是由第一个问题所覆盖,但我足够的好奇,一下也无妨具体问:是否曾经需要使用围栏与thread_local访问?(如果有的话,我希望只有编译器的围栏atomic_signal_fence才能成为首选工具.)

谢谢.

c++ atomic memory-fences memory-barriers c++11

32
推荐指数
1
解决办法
3300
查看次数

Java中的内存屏障行为

在阅读了更多的博客/文章等之后,我现在对内存屏障之前/之后的加载/存储行为感到困惑.

以下是Doug Lea在他关于JMM的一篇澄清文章中的两个引用,它们都非常简单:

  1. 线程A在写入易失性字段f时可见的任何内容在读取f时都会对线程B可见.
  2. 请注意,两个线程都必须访问相同的volatile变量才能正确设置before-before关系.情况并非如此,线程A在写入易失性字段f时可见的所有内容在读取易失性字段g后变为线程B可见.

但是当我查看另一篇关于内存障碍的博客时,我得到了这些:

  1. 商店屏障,x86上的"sfence"指令强制屏障之前的所有存储指令发生在屏障之前,并将存储缓冲区刷新到缓存以供发布它的CPU.
  2. 负载屏障,在x86"lfence"指令,强制所有加载指令之后的阻隔后发生障碍,然后等待负载缓冲区对于CPU流失.

对我来说,Doug Lea的澄清比另一个更严格:基本上,这意味着如果负载屏障和存储屏障位于不同的监视器上,则无法保证数据的一致性.但后者意味着即使屏障位于不同的监视器上,数据的一致性也会得到保证.我不确定我是否正确理解这两个,而且我不确定它们中的哪一个是正确的.

考虑以下代码:

  public class MemoryBarrier {
    volatile int i = 1, j = 2;
    int x;

    public void write() {
      x = 14; //W01
      i = 3;  //W02
    }

    public void read1() {
      if (i == 3) {  //R11
        if (x == 14) //R12
          System.out.println("Foo");
        else
          System.out.println("Bar");
      }
    }

    public void read2() {
      if (j == 2) {  //R21
        if (x == 14) //R22
          System.out.println("Foo");
        else
          System.out.println("Bar"); …
Run Code Online (Sandbox Code Playgroud)

java multithreading java-memory-model memory-barriers

32
推荐指数
1
解决办法
5868
查看次数

原子能遭受虚假商店吗?

在C++中,原子能遭受虚假存储吗?

例如,假设mn是原子能和m = 5最初.在主题1中,

    m += 2;
Run Code Online (Sandbox Code Playgroud)

在线程2中,

    n = m;
Run Code Online (Sandbox Code Playgroud)

结果:最终值n应为5或7,对吧?但它可能是虚假的6吗?它是虚假的4或8,甚至是其他什么?

换句话说,C++内存模型是否禁止线程1表现得好像这样做?

    ++m;
    ++m;
Run Code Online (Sandbox Code Playgroud)

或者,更奇怪的是,好像它做到了这一点?

    tmp  = m;
    m    = 4;
    tmp += 2;
    m    = tmp;
Run Code Online (Sandbox Code Playgroud)

参考文献:H.-J.Boehm&SV Adve,2008,图1.(如果您点击链接,那么,在论文的第1部分中,查看第一个项目符号项:"......提供的非正式规范")

替代形式的问题

一个答案(赞赏)表明,上述问题可能会被误解.如果有帮助,那么这是另一种形式的问题.

假设程序员试图告诉线程1 跳过操作:

    bool a = false;
    if (a) m += 2;
Run Code Online (Sandbox Code Playgroud)

C++内存模型是否禁止线程1在运行时表现,就好像它这样做?

    m += 2; // speculatively alter m
    m -= 2; // oops, should not have altered! reverse the alteration
Run Code Online (Sandbox Code Playgroud)

我问,因为之前联系过的Boehm和Adve似乎解释说多线程执行可以

  • 推测性地改变变量,但随后
  • 稍后,当推测性更改变得不必要时,将变量更改回其原始值.

可编译的示例代码

如果您愿意,这里有一些您可以实际编译的代码.

#include …
Run Code Online (Sandbox Code Playgroud)

c++ multithreading atomic memory-barriers

31
推荐指数
3
解决办法
1960
查看次数

std :: mutex会创建一个围栏​​吗?

如果我锁定std::mutex遗嘱,我总会得到一个记忆围栏?我不确定它是暗示还是强迫你获得围栏.

更新:

根据RMF的评论发现此参考文献.

多线程编程和内存可见性

c++ multithreading mutex memory-model memory-barriers

29
推荐指数
2
解决办法
3717
查看次数