Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/278.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#_.net_Multithreading_Mono_Thread Safety - Fatal编程技术网

C# 对象数组,在一个线程中更新,在另一个线程中读取

C# 对象数组,在一个线程中更新,在另一个线程中读取,c#,.net,multithreading,mono,thread-safety,C#,.net,Multithreading,Mono,Thread Safety,简化了这个问题,让我能更清楚地表达我的实际问题 我有两个线程,分别称为A和B。它们共享一个类型为Foo的对象,该对象有一个名为Name的字段,存储在索引0处的Foo[]类型数组中。线程总是以系统已经保证的顺序访问索引0,因此在线程a之前不存在线程B的争用条件 命令是这样的 // Thread A array[0].Name = "Jason"; // Thread B string theName = array[0].Name 正如我所说,这个顺序已经得到保证,线程B无法在线程A之

简化了这个问题,让我能更清楚地表达我的实际问题

我有两个线程,分别称为
A
B
。它们共享一个类型为
Foo
的对象,该对象有一个名为
Name
的字段,存储在索引
0
处的
Foo[]
类型数组中。线程总是以系统已经保证的顺序访问索引
0
,因此在线程
a
之前不存在线程
B
的争用条件

命令是这样的

 // Thread A
 array[0].Name = "Jason";

 // Thread B
 string theName = array[0].Name
正如我所说,这个顺序已经得到保证,线程B无法在线程A之前读取值

我要确保的是两件事:

  • 两个线程都获取索引0处的最新对象
  • 线程
    B
    总是在.Name字段中获取最新的值
  • Name
    标记为volatile不是一个选项,因为真实对象要复杂得多,甚至具有自定义结构,而这些结构甚至不能附加volatile属性

    现在,满足1很容易(总是获得最新的对象),您可以执行.volatireRead:

     // Thread A
     Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
     obj.Name = "Jason";
    
     // Thread B
     Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
     string theName = obj.Name
    
    或者,您可以插入内存屏障:

     // Thread A
     array[0].Name = "Jason";
     Thread.MemoryBarrier();
    
     // Thread B
     Thread.MemoryBarrier();
     string theName = array[0].Name
    

    所以我的问题是:这是否足以满足条件2?我总是从我读取的对象的字段中获取最新的值?如果索引
    0
    处的对象未更改,但
    名称已更改。在索引
    0
    上执行
    VolatileRead
    MemoryBarrier
    是否会确保索引
    0
    处的对象中的所有字段也获得其最新值?

    这些解决方案、锁
    volatile
    都不能解决您的问题。因为:

  • volatile
    确保一个线程更改的变量对在相同数据上运行的其他线程立即可见(即,它们不会被缓存),并且对该变量的操作不会重新排序。不是你真正需要的
  • lock
    确保写入/读取不会同时发生,但不能保证写入/读取的顺序。它取决于哪个线程首先获得锁,这是不确定的
  • 因此,如果您的流程是:

    Thread A read Name
    Thread A modify Name
    Thread B read Name
    
    正是按照该顺序,您需要使用事件强制执行它(例如,
    AutoresetEvent
    ):

    这保证了线程B在A修改名称变量之前不会读取该变量

    编辑:好的,因此您可以确保遵守上述流程。由于您说不能声明字段
    volatile
    ,因此我建议使用
    Thread.MemoryBarrier()
    引入强制排序的围栏:

    //Thread A
    foo[0].Name = "John"; // write value
    Thread.MemoryBarrier();
    
    //Thread B
    Thread.MemoryBarrier();
    string name = foo[0].Name; // read value
    

    有关更多信息,请查看此文档:

    如果我理解正确,锁将解决您的问题。这是因为锁在自身周围会产生隐式(完整)内存障碍


    您还可以使用
    Thread.MemoryBarrier
    显式使用内存屏障。阅读更多。在x86上很难注意到内存屏障效应,但在更宽松的排序系统(如PPC)上,它通常是非常重要的。

    为什么要使用数组?查看名称空间。首先,不要使用.NET4。据我所知,并发的东西只保证集合本身的内容,而不保证集合中对象的可能字段/属性。我认为volatile不能保证你所认为的那样。如果不能保证插入数组的对象是线程安全的,我想你得做一些登记系统。我可能会使用并发命名空间中的一个集合来实现它。创建一个IDisposable和泛型的新类型,具有公共T值。每当一个线程处理完该项时,它都会处理它,此时您会允许其他线程读取它。只是一个想法。另一个想法是只允许线程安全的项目进入自定义集合。您只需在列表或T[]周围放置一个通用包装器,并具有一个T为IThreadSafe的约束(它没有任何成员,但对您和其他开发人员来说是一个提醒)。这不是问题,两个线程不能同时访问它。曾经我认为人们没有理解我的问题,我需要重写它。访问将始终是顺序的,但来自两个(或可能更多)不同的线程。@thr:来自以下语句:“现在,为了能够保证Thread1获得存储在Foo的Name字段中的新值…”我知道只有在Thread0完成更新后,您才需要Thread1读取名称。是的,我认为人们不理解我。Thread1将始终在Thread0写入.Name后访问该值,这已经得到系统的保证。我担心的是Thread1会得到Thread0编写的值。不存在竞态条件。@thr:那么您所需要的就是易失性。请看我在回答的第1点中所写的内容。我来编辑一下。
    //Thread A
    foo[0].Name = "John"; // write value
    Thread.MemoryBarrier();
    
    //Thread B
    Thread.MemoryBarrier();
    string name = foo[0].Name; // read value