Volatile.Read和Volatile.Write背后的逻辑是什么?

时间:2017-01-11 03:27:10

标签: c# .net multithreading concurrency parallel-processing

来自MSDN,Volatile.Read()

  

读取字段的值。在需要它的系统上,插入一个   内存屏障,阻止处理器重新排序内存   操作如下:如果在此方法之后出现读或写   代码,处理器无法移动它之前这种方法。

Volatile.Write()

  

将值写入字段。在需要它的系统上,插入一个   内存屏障,阻止处理器重新排序内存   操作如下:如果在此方法之前出现读取或写入   在代码中,处理器无法在此方法之后移动它。

我想我可以理解Volatile.Read()Volatile.Write()的使用场景,并看到很多例子解释了为什么这两种方法有助于确保程序的正确性。

但我仍然想知道,这些规则背后的逻辑是什么?

Volatile.Read()为例,为什么它需要之后的操作才能之前移动,但不需要操作之前 / strong>它?

还有为什么它与Volatile.Write()相反?

谢谢!

3 个答案:

答案 0 :(得分:3)

易失性读取和易失性写入的保证确保如果一个线程使用易失性写入来指示某些事情已完成,然后另一个线程使用易失性读取来注意某些事情已完成,那么第二个线程将看到那种东西的全部效果。

例如,假设Thread1初始化对象A,而不是向flag写入易失性指示它已完成。初始化对象A的字段所涉及的所有内存操作都发生在代码中的标志设置之前。保证这些“在volatile写入后无法移动”到flag,所以当标志在内存中设置时,整个初始化对象在内存中,其他线程可以看到它。

现在让我们说Thread2正在等待那个对象。它有一个易失性读取,看到flag被设置,然后读取A的字段并根据它读取的内容做出决定。这些读取操作发生在代码中的易失性读取之后,并且易失性读取保证确保它们将在内存中的易失性读取之后发生,以便Thread2保证看到对象{{1}的完全初始化字段},而不是之前存在的任何东西。

所以:在A的易失性写入之前,Thread1写入内存的写入显然必须在flag之前发送到内存中才能读取它,以及以下内容在Thread2之后读取,因此它会看到正确初始化的对象。

这就是为什么写入不能延迟超过易失性写入,并且在易失性读取之前读取不能被提升。反之亦然?

嗯,让我们说Thread2,在看到Thread2被初始化之后,做了一些工作并将其写入A用于决定如何初始化Thread1的内存中。 1}}。这些写入保证不会发生在内存中,直到 A看到Thread2完成后A对这些位置的读取才会保证发生之前 Thread1在内存中设置,因此保证flag的写入不会干扰初始化工作。

答案 1 :(得分:2)

这些规则背后的逻辑被称为记忆模型。
在.NET中我们有非常弱的内存模型(参见ECMA-335),这意味着允许编译器,jit和cpu进行大量优化(只要它们保持单线程语义和易失性语义)和它在优化的可能性方面非常棒。
它允许编译器/ jit / cpu进行任何优化,只要它们满足以下条件:

  

符合CLI的实现可以自由执行程序   在单个线程中使用任何保证的技术   执行,线程产生的副作用和异常   以CIL指定的顺序可见。仅为此目的   易失性操作(包括易失性读取)构成可见   副作用。 (注意,虽然只有易失性操作构成   可见的副作用,挥发性操作也会影响能见度   非易失性参考文献。)

这意味着除非您使用隐式或显式volatile操作,否则假定您的所有代码都是单线程的。
例如,

  

获取锁定(System.Threading.Monitor.Enter或输入   synchronized方法)应隐式执行易失性读取   操作,并释放锁(System.Threading.Monitor.Exit或   保留同步方法)应隐式执行volatile   写操作。

这意味着它无法移动上面的任何操作(来自lock语句)(隐式Volatile.Read会阻止此操作)并且无法将它们移动到锁定之下(隐式Volatile.Write可以防止)这个)。因此它们保持在lock语句中,但它仍然可以在这个锁定语句中重新排序或优化。

答案 2 :(得分:0)

第一次

<块引用>

但我还是想知道,这些规则背后的逻辑是什么?

Described by Matt Timmermans

第二个

<块引用>

以Volatile.Read()为例,为什么它需要在它之前不能移动之后的操作,而它之前的操作却不需要任何东西?

让我们看看根据 volatile 语义有什么可能。

Read                                       Write         
➖➖➖➖➖                               ➖➖➖➖➖
** later cannot move here  **              Some.X = q      
MemoryBarier                               MemoryBarier  
x = Some.X                                 ** sooner cannot move here **


Execution orders
〰〰〰〰〰〰〰〰〰
Write (sooner)                             Read  (sooner)
Read  (later)                              Write (later)


Reorder                                    Reorder
➖➖➖➖➖                              ➖➖➖➖➖
** later cannot move here  **              Not possible
MemoryBarier ¦ (later)
x = Some.X   ¦ (later)
Some.X = q   ¦ (sooner)
MemoryBarier ¦ (sooner)
** sooner cannot move here **

您可以看到 volatile readwrite 的规则可能有点违反直觉。

查看“之前”可能性的表格

Read           |  Write          |  Volatile.Write  |  Volatile.Read
Volatile.Read  |  Volatile.Read  |  Volatile.Read   |  Volatile.Read
---------------|-----------------|------------------|-------------------
No harm when   | Can be          | Can be reordered.| Cannot be reordered.
reordered.     | reordered.      | Can be a harm.   |
One should use | Missing MT tech.|                  |
some MT        | Can be a harm.  |                  |
technics.      |                 |                  |            

所以任何合理的before指令都已经受到volatility规则的约束。

第三

<块引用>

还有为什么它与 Volatile.Write() 相反?

每一个都是互补的。因此,您可以根据需要以 read 的方式writevolatile