为什么在C#中进行多线程处理会使bool变得易变?

时间:2015-05-16 06:23:57

标签: c# multithreading

我非常了解C ++中的volatile关键字。但在C#中,它似乎采用了不同的含义,与多线程更相关。我认为bool操作是原子的,我认为如果操作是原子的,你就不会有线程问题。我错过了什么?

https://msdn.microsoft.com/en-us/library/x13ttww7.aspx

2 个答案:

答案 0 :(得分:4)

  

我认为bool操作是原子的

他们确实是原子的。

  

我认为如果操作是原子的,你就不会有线程问题。

这是你的照片不完整的地方。想象一下,您有两个线程在不同的核心上运行,每个核心都有自己的缓存层。线程#1在其缓存中有foo,而线程#2更新foo的值。线程#1不会看到更改,除非foo标记为volatile,获得lock,使用Interlocked类或明确调用{{1}这会导致值在缓存中失效。因此,guaranteeing that you read the most up to date value

  

使用volatile修饰符可确保一个线程检索另一个线程写入的最新值。

Eric Lippert有一个关于波动的great post,他解释说:

  

volatile读取的真正语义   写作比我在这里概述的要复杂得多;在   事实上,他们实际上并不能保证每个处理器都能阻止它   正在进行和更新主存储器的缓存。相反,它们提供   关于读取和读取之前和之后内存访问的方式的较弱保证   可以观察到相对于彼此的订单。

修改

根据@xanatos评论,并不意味着 Thread.MemoryBarrier()保证立即读取,它保证读取最新的值。

答案 1 :(得分:3)

C#中的volatile关键字是关于读/写重新排序的,所以它是非常深奥的。

http://www.albahari.com/threading/part4.aspx#_Memory_Barriers_and_Volatility

(我认为是#34;圣经"关于线程之一)写道:

  

volatile关键字指示编译器在每次从该字段读取时生成获取栅栏,并在每次写入该字段时生成释放栅栏。获取栅栏可防止其他读/写在栅栏前移动;释放栅栏可防止在栅栏后移动其他读/写。这些“半栅栏”比完全栅栏更快,因为它们为运行时和硬件提供了更多的优化空间。

这是一件非常难以理解的事情: - )

现在......它的意思并非:

  • 这并不意味着将会读取一个值现在 /将写入现在

它只是意味着如果你从一个易变的变量中读取一些东西,那么在此之前已经读过/写过的所有其他东西"特殊的"读完后不会被移动"特别"读。所以它创造了一个障碍。矛盾的是,通过读取一个易变量变量,您可以保证在读取时对所有其他变量(易失或不变)所做的所有写操作都将完成。

易失性写入可能更重要,并且部分由英特尔CPU保证,而第一版Java无法保证:没有写入重新排序。问题是:

object myrefthatissharedwithotherthreads = new MyClass(5);

,其中

class MyClass
{ 
    public int Value; 
    MyClass(int value) 
    { 
        Value = value; 
    } 
}

现在......表达可以想象成:

var temp = new MyClass();
temp.Value = 5;
myrefthatissharedwithotherthreads = temp;

其中temp是编译器生成的,您无法看到的内容。

如果可以重新排序写入,则可以:

var temp = new MyClass();
myrefthatissharedwithotherthreads = temp;
temp.Value = 5;

,另一个线程可以看到部分初始化的MyClass,因为myrefthatissharedwithotherthreads的值在类MyClass完成初始化之前是可读的。