来自MSDN,Volatile.Read()
:
读取字段的值。在需要它的系统上,插入一个 内存屏障,阻止处理器重新排序内存 操作如下:如果在此方法之后出现读或写 代码,处理器无法移动它之前这种方法。
和Volatile.Write()
:
将值写入字段。在需要它的系统上,插入一个 内存屏障,阻止处理器重新排序内存 操作如下:如果在此方法之前出现读取或写入 在代码中,处理器无法在此方法之后移动它。
我想我可以理解Volatile.Read()
和Volatile.Write()
的使用场景,并看到很多例子解释了为什么这两种方法有助于确保程序的正确性。
但我仍然想知道,这些规则背后的逻辑是什么?
以Volatile.Read()
为例,为什么它需要之后的操作才能之前移动,但不需要操作之前 / strong>它?
还有为什么它与Volatile.Write()
相反?
谢谢!
答案 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)
但我还是想知道,这些规则背后的逻辑是什么?
以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
read
和 write
的规则可能有点违反直觉。
查看“之前”可能性的表格
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
的方式write
和 volatile
。