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之前读取值
我要确保的是两件事:
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