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

本文关键字:线程 读取 更新 另一个 一个 数组 对象 | 更新日期: 2023-09-27 18:18:06

将问题简化,以便更清楚地表示我实际上在问什么

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

顺序是这样的。

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

正如我所说,这个顺序已经得到保证,线程B没有办法在线程A

之前读取值

我想确保的是两件事:

  1. 两个线程都获得索引为0的最新对象。
  2. 线程B总是获取。name字段中的最新值

Name标记为volatile不是一个选项,因为真正的对象要复杂得多,甚至有自定义结构,甚至不能将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

或者你可以插入一个内存屏障:

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

所以我的问题是:这是否也足以满足条件2?我总是从我读出的对象的字段中获得最新的值?如果索引0的对象没有改变,但是索引Name改变了。在索引0上做VolatileReadMemoryBarrier确保索引0的对象中的所有字段也得到它们的最新值吗?

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

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

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

因此,如果你的流是:

Thread A read Name
Thread A modify Name
Thread B read Name

完全按照这个顺序,你需要用一个事件(例如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

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

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

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

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

Locks将解决您遇到的问题,如果我理解正确的话。这是因为锁在自身周围产生隐式的(全)内存屏障。

您也可以使用Thread.MemoryBarrier显式地使用内存屏障。点击这里阅读更多内容。在x86上很难注意到内存屏障效应,但在更宽松的排序系统(如PPC)上,它通常是显著的。