c#struct array的并发性

Dan*_*anH 2 c# concurrency struct volatile memory-barriers

给定一个struct数组:

public struct Instrument
{
    public double NoS;
    public double Last;
}

var a1 = new Instrument[100];
Run Code Online (Sandbox Code Playgroud)

并且一个线程任务池正在写入这些元素,因为单个元素最多可以同时由两个线程写入,每个双字段一个(有效地通过主题进行上游排队).

并且知道双重可以在64位上原子地写入.(编辑这个错误地说原来是32位)

我需要使用数组中的所有值定期执行计算,并且我希望它们在计算期间保持一致.

所以我可以使用以下方法对数组进

var snapshot = a1.Clone();
Run Code Online (Sandbox Code Playgroud)

现在我的问题是关于同步的具体细节.如果我使成员易变,我认为这根本不会帮助克隆,因为读/写aquire/releases不在数组级别.

现在我可以有一个数组锁,但这会在最常见的数据写入数组的过程中引起很多争论.所以不太理想.

或者我可以有一个每行锁定,但这将是一个真正的痛苦,因为他们都需要在克隆之前被获取,同时我有所有备份的写入.

现在,如果快照没有最新值(如果它只是微秒等)并不重要,所以我想我可能会因为没有锁而逃脱.我唯一担心的是,是否存在持续时间段内没有缓存写回的情况.这是我应该担心的吗?编写器在TPL数据流中,唯一的逻辑是在结构中设置两个字段.我真的不知道函数范围是如何或者是否与缓存回写相关联.

思考/建议吗?

编辑:如果我使用互锁写入结构中的变量怎么样?

edit2:写入量比读取量高很多.写入Nos&Last字段还有两个单独和并发的服务.所以他们可以同时写入.这导致参考对象方法存在原子性问题.

edit3:更多细节.假设数组是30-1000个元素,每个元素可以每秒多次更新.

Ste*_*ven 5

由于Instrument包含两个双精度数(两个64位值),因此无法以原子方式编写它(即使在64位计算机上).这意味着该Clone方法永远不能在不进行某种同步的情况下制作线程安全的副本.

TLDR; 不要使用结构,使用不可变类.

通过小型重新设计,您可能会有更多的运气.尝试使用.NET框架中的不可变数据结构和并发集合.例如,创建Instrument一个不可变类:

// Important: Note that Instrument is now a CLASS!! 
public class Instrument
{
    public Instrument(double nos, double last)
    {
        this.NoS = nos;
        this.Last = last;
    }

    // NOTE: Private setters. Class can't be changed
    // after initialization.
    public double NoS { get; private set; }
    public double Last { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

这种方式更新Instrument意味着你必须创建一个新的,这使得更容易推理这个.当您确定只有一个线程正在使用单个线程时,Instrument您就完成了,因为工作者现在可以安全地执行此操作:

Instrument old = a[5];

var newValue = new Instrument(old.NoS + 1, old.Last - 10);

a[5] = newValue;
Run Code Online (Sandbox Code Playgroud)

因为,引用类型是32位(或64位机器上的64位),更新引用保证是原子的.克隆现在总是会产生正确的副本(它可能缺少,但这似乎不是你的问题).

UPDATE

在重新阅读你的问题之后,我发现我误解了它,因为一个线程没有写入Instrument,而是写入一个工具值,但解决方案实际上是相同的:使用不可变引用类型.例如,一个简单的技巧是将NoSLast属性的支持字段更改为对象.这使得将它们更新为原子:

// Instrument can be a struct again.
public struct Instrument
{
    private object nos;
    private object last;

    public double NoS
    {
        get { return (double)(this.nos ?? 0d); }
        set { this.nos = value; }
    }

    public double Last
    {
        get { return (double)(this.last ?? 0d); }
        set { this.last = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

更改其中一个属性时,该值将被装箱,并且装箱值是不可变的引用类型.这样您就可以安全地更新这些属性.