Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/289.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#易失性变量:内存围栏与缓存_C#_Caching_Volatile_Memory Fences - Fatal编程技术网

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位。它们还保证是原子的,以供参考。对于其他类型,例如long
struct
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();
    }
}