MemoryBarrier是否保证所有内存的内存可见性?

时间:2016-09-20 19:46:34

标签: c# multithreading

如果我理解正确,在C#中,lock块保证对一组指令的独占访问,但它也保证来自内存的任何读取都反映了任何CPU缓存中该内存的最新版本。我们认为lock块是保护块内读取和修改的变量,这意味着:

  1. 假设您已经在必要时正确实施了锁定,那么这些变量一次只能被一个线程读取和写入,并且
  2. lock块内读取,查看变量的最新版本,lock块内的写入对所有线程都可见。
  3. (右?)

    这第二点是我感兴趣的。是否存在一些魔法,其中仅在lock保护的代码中读取和写入的变量保证是新鲜的,或者在lock的实现中使用的内存障碍保证所有内存现在对所有线程都同样新鲜吗?请原谅我在这里关于缓存如何工作的心理模糊性,但我已经知道缓存存在多个多字节"线"数据的。我想我要问的是,内存屏障会强制同步 all " dirty"缓存行或只是一些,如果只是一些,什么决定哪些行得到同步?

2 个答案:

答案 0 :(得分:7)

  

如果我理解正确,在C#中,锁定块可以保证对一组指令的独占访问......

右。规范保证。

  

但它也保证来自内存的任何读取都反映了任何CPU缓存中该内存的最新版本。

C#规范对“CPU缓存”没有任何说明。您已经离开了规范所保证的范围,并进入了实现细节的领域。不要求C#的实现在具有任何特定缓存体系结构的CPU上执行。

  

是否存在一些魔力,只有在锁定块保护的代码中读取和写入的变量才能保证新鲜,或者锁定实现中使用的内存屏障是否保证所有内存现在对所有线程都是新鲜的?

不要试图解析你的问题或者问题,而是说实际上语言所保证的是什么。一个特殊的效果是:

  • 对变量的任何写入,volatile或不是
  • 读取易失性字段
  • 任何投掷

特殊效果的顺序在某些特殊点保留:

  • 读取和写入易失性字段
  • 线程创建和终止

运行时需要确保特殊效果与特殊点一致。因此,如果在锁定之前读取易失性字段,并且在写入之后读取,则在写入之后不能移动读取。

那么,运行时如何实现这一目标?打败了我。但运行时肯定不需要“保证所有线程的所有内存都是新鲜的”。运行时需要确保某些读取,写入和抛出按特定点的时间顺序发生,这就是全部。

运行时特别要求所有线程都遵循相同的顺序

最后,我总是通过指向你来结束这些讨论:

http://blog.coverity.com/2014/03/26/reordering-optimizations/

阅读完之后,你应该欣赏即使在x86上也会发生的各种可怕的事情,当你偶然做出关于锁定的事情时。

答案 1 :(得分:6)

  

在锁定块中读取,查看变量的最新版本,并且锁定块内的写入对所有线程都可见。

不,这绝对是一种有害的过度简化。

当您输入lock语句时,会有一个排序的内存栏,这意味着您将始终读取“新鲜”数据。当你退出lock状态时,会有一个内存栅栏排序意味着你写的所有数据都保证写入主内存并可供其他线程使用。

重要的一点是,如果多个线程在“拥有”特定锁时只读/写内存,那么按照定义其中一个将在下一个进入锁之前退出锁...所以那些读取和写入都将是简单而正确的。

如果您的代码在没有锁定的情况下读取和写入变量,则无法保证它将“看到”由行为良好的代码(即使用锁定的代码)写入的数据,或者表现良好的线程将“看到”该坏代码所写的数据。

例如:

private readonly object padlock = new object();
private int x;

public void A()
{
    lock (padlock)
    {
        // Will see changes made in A and B; may not see changes made in C
        x++;
    }
}

public void B()
{
    lock (padlock)
    {
        // Will see changes made in A and B; may not see changes made in C
        x--;
    }
}

public void C()
{
    // Might not see changes made in A, B, or C. Changes made here
    // might not be visible in other threads calling A, B or C.
    x = x + 10;
}

现在它比这更微妙,但这就是为什么使用公共锁来保护一组变量的原因。