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

thr*_*thr 5 .net c# mono multithreading thread-safety

简化了问题,以更清楚地表达我实际要求的内容

我有两个线程,叫他们AB.它们共享一个类型的对象,Foo该对象具有一个被调用的字段Name,并存储Foo[]在索引处的类型数组中0.线程将始终0按照系统保证的顺序访问索引,因此线程B之前没有线程获取的竞争条件A.

顺序是这样的.

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

 // Thread B
 string theName = array[0].Name
Run Code Online (Sandbox Code Playgroud)

正如我所说,这个顺序已经得到保证,线程B无法在线程A之前读取该值

我想要确保的是两件事:

  1. 两个线程都获得索引0处的最新对象.
  2. 线程B始终在.Name字段中获取最新值

标记Name为易失性不是一种选择,因为真实对象要复杂得多,甚至还有自定义结构甚至不能附加volatile属性.

现在,满足1很容易(总是得到最新的对象),你可以做一个.VolatileRead:

 // 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
Run Code Online (Sandbox Code Playgroud)

或者您可以插入内存屏障:

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

 // Thread B
 Thread.MemoryBarrier();
 string theName = array[0].Name
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:这还足以满足条件2吗?我总是从我读出的对象的字段中获得最新值?如果索引处的对象0没有改变,但是Name已经改变了.执行a VolatileReadMemoryBarrieron索引0会确保索引对象中的所有字段0也获取其最新值吗?

Tud*_*dor 2

这些解决方案lockvolatile不能解决您的问题。因为:

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

因此,如果您的流程是:

Thread A read Name
Thread A modify Name
Thread B read Name
Run Code Online (Sandbox Code Playgroud)

正是按照这个顺序,您需要通过事件来强制执行它(AutoresetEvent例如):

//Thread A
foo[0].Name = "John"; // write value
event.Set(); // signal B that write is completed

//Thread B
event.WaitOne(); // wait for signal
string name = foo[0].Name; // read value
Run Code Online (Sandbox Code Playgroud)

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

编辑:好的,所以您确定遵守上述流程。既然你说你不能声明 fields volatile,我建议使用Thread.MemoryBarrier()来引入强制排序的栅栏:

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

//Thread B
Thread.MemoryBarrier();
string name = foo[0].Name; // read value
Run Code Online (Sandbox Code Playgroud)

有关更多信息,请查看此文档:http://www.albahari.com/threading/part4.aspx