C#易失性变量:内存围栏与缓存
所以我研究这个话题已经有相当一段时间了,我想我理解了最重要的概念,比如释放和获取内存围栏 然而,对于C#易失性变量:内存围栏与缓存,c#,caching,volatile,memory-fences,C#,Caching,Volatile,Memory Fences,所以我研究这个话题已经有相当一段时间了,我想我理解了最重要的概念,比如释放和获取内存围栏 然而,对于volatile与主存缓存之间的关系,我还没有找到令人满意的解释 因此,我了解到,volatile字段的每一次读写都会强制执行读操作的严格顺序,以及前后的写操作(读获取和写释放)。但这只能保证操作的排序。它没有说明这些更改对其他线程/处理器可见的时间。特别是,这取决于刷新缓存的时间(如果有)。我记得我读过Eric Lippert的一篇评论,大意是“存在volatile字段会自动禁用缓存优化”。但我
volatile
与主存缓存之间的关系,我还没有找到令人满意的解释
因此,我了解到,volatile
字段的每一次读写都会强制执行读操作的严格顺序,以及前后的写操作(读获取和写释放)。但这只能保证操作的排序。它没有说明这些更改对其他线程/处理器可见的时间。特别是,这取决于刷新缓存的时间(如果有)。我记得我读过Eric Lippert的一篇评论,大意是“存在volatile
字段会自动禁用缓存优化”。但我不确定这到底意味着什么。这是否意味着仅仅因为我们在某个地方有一个volatile
字段,整个程序的缓存就被完全禁用了?如果不是,那么禁用缓存的粒度是多少
此外,我还读到了一些关于强易失性和弱易失性语义的内容,C#遵循强语义,即无论是否是
易失性
字段,每次写入都将直接进入主内存。我对这一切都很困惑。是的,volatile
是关于围栏的,围栏是关于订购的。
所以什么时候?不在范围内,实际上是所有层(编译器、JIT、CPU等)组合的实现细节,
但每一次实施都应该有一个体面而实际的答案来回答这个问题
我阅读了规范,他们没有提到另一个线程是否会观察到一个易失性写操作(易失性读或不读)。对不对
让我重新表述一下问题:
说明书中没有说明这一点,对吗
没有。关于这件事,说明书是很清楚的
是否保证在另一个线程上观察到易失性写操作
是,如果另一个线程具有关键执行点。保证观察到一个特殊的副作用,并针对一个关键执行点进行排序
易失性写入是一种特殊的副作用,许多事情都是关键的执行点,包括启动和停止线程。请参见规范以获取此类组件的列表
例如,假设线程Alpha将volatile int字段v
设置为1,并启动线程Bravo,该线程读取v
,然后加入Bravo。(也就是说,Bravo上的块完成。)
在这一点上,我们有一个特殊的副作用——写入——一个关键的执行点——线程启动——和第二个特殊的副作用——易失性读取。因此,需要Bravo从v
读取一个。(当然,假设没有其他线程同时编写了它。)
Bravo现在将v
增加到两端。这是一个特殊的副作用——写操作——和一个关键的执行点——线程的结束
当线程Alpha现在恢复并执行v
的易变读取时,需要读取两个。(当然,假设在此期间没有其他线程写入它。)
必须保留Bravo写入和终止的副作用顺序;显然,在Bravo终止之前,Alpha不会再次运行,因此需要观察写入操作。我将首先解决最后一个问题。Microsoft的.NET实现在writes1上具有发布语义。这不是C#本身,所以同一个程序,不管是哪种语言,在不同的实现中都可能有弱的非易失性写操作 副作用的可见性与多线程有关。忘掉CPU、内核和缓存吧。相反,假设每个线程都有堆上内容的快照,需要某种同步来在线程之间传递副作用 那么,C#说了什么?()与公共语言基础结构标准(CLI;和)基本相同,但有一些不同。我以后再谈 那么,CLI怎么说?只有不稳定的操作才有明显的副作用 注意,它还说堆上的非易失性操作也是副作用,但不能保证是可见的。同样重要的是,它并没有声明它们保证不可见 易失性操作到底发生了什么?易失性读取具有获取语义,它位于任何后续内存引用之前。易失性写入具有释放语义,它遵循任何前面的内存引用 获取锁执行易失性读取,释放锁执行易失性写入
联锁
操作具有获取和释放语义
还有一个重要的术语需要学习,那就是原子性
在32位体系结构上,读写(无论是否易失性)保证在原语值上是原子的,在32位体系结构上最多为32位,在64位体系结构上最多为64位。它们还保证是原子的,以供参考。对于其他类型,例如longstruct
s,操作不是原子的,它们可能需要多个独立的内存访问
然而,即使使用易变语义,读-修改-写操作,例如v++=1
或等效的++v
(或v++
,就副作用而言)也不是原子操作
联锁操作保证了某些操作的原子性,通常是加法、减法、比较和交换(CAS),即当且仅当当前值仍然是某个预期值时写入某个值。NET还有一个原子Re
public class InefficientEvent
{
private volatile bool signalled = false;
public Signal()
{
signalled = true;
}
public InefficientWait()
{
while (!signalled)
{
}
}
}
var currentValue = Volatile.Read(ref field);
var newValue = GetNewValue(currentValue);
var oldValue = currentValue;
var spinWait = new SpinWait();
while ((currentValue = Interlocked.CompareExchange(ref field, newValue, oldValue)) != oldValue)
{
spinWait.SpinOnce();
newValue = GetNewValue(currentValue);
oldValue = currentValue;
}
object local = field;
if (local != null)
{
// code that reads local
}
if (field != null)
{
// code that replaces reads on local with reads on field
}
object local2 = local1;
if (local2 != null)
{
// code that reads local2 on the assumption it's not null
}
if (local1 != null)
{
// code that replaces reads on local2 with reads on local1,
// as long as local1 and local2 have the same value
}
var local = field;
local?.Method()
var local = field;
var _temp = local;
(_temp != null) ? _temp.Method() : null
var local = field;
(local != null) ? local.Method() : null
(field != null) ? field.Method() : null
public class Worker
{
private bool working = false;
private bool stop = false;
public void Start()
{
if (!working)
{
new Thread(Work).Start();
working = true;
}
}
public void Work()
{
while (!stop)
{
// TODO: actual work without volatile operations
}
}
public void Stop()
{
stop = true;
}
}
public void Work()
{
bool localStop = stop;
while (!localStop)
{
// TODO: actual work without volatile operations
}
}
using System;
using System.Threading;
public class SurrealVolatileSynchronizer
{
public volatile bool v1 = false;
public volatile bool v2 = false;
public int state = 0;
public void DoWork1(object b)
{
var barrier = (Barrier)b;
barrier.SignalAndWait();
Thread.Sleep(100);
state = 1;
v1 = true;
}
public void DoWork2(object b)
{
var barrier = (Barrier)b;
barrier.SignalAndWait();
Thread.Sleep(200);
bool currentV2 = v2;
Console.WriteLine("{0}", state);
}
public static void Main(string[] args)
{
var synchronizer = new SurrealVolatileSynchronizer();
var thread1 = new Thread(synchronizer.DoWork1);
var thread2 = new Thread(synchronizer.DoWork2);
var barrier = new Barrier(3);
thread1.Start(barrier);
thread2.Start(barrier);
barrier.SignalAndWait();
thread1.Join();
thread2.Join();
}
}