对象数组,在一个线程中更新,在另一个线程中读取
本文关键字:线程 读取 更新 另一个 一个 数组 对象 | 更新日期: 2023-09-27 18:18:06
将问题简化,以便更清楚地表示我实际上在问什么
我有两个线程,分别称为A
和B
。它们共享一个类型为Foo
的对象,该对象有一个名为Name
的字段,并存储在索引为0
的类型为Foo[]
的数组中。线程总是按照系统已经保证的顺序访问索引0
,因此不存在线程B
在线程A
之前获得的竞争条件。
顺序是这样的。
// Thread A
array[0].Name = "Jason";
// Thread B
string theName = array[0].Name
正如我所说,这个顺序已经得到保证,线程B没有办法在线程A
之前读取值我想确保的是两件事:
- 两个线程都获得索引为0的最新对象。
- 线程
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
上做VolatileRead
或MemoryBarrier
确保索引0
的对象中的所有字段也得到它们的最新值吗?
这些解决方案,lock
或volatile
都不能解决您的问题。因为:
-
volatile
确保一个线程更改的变量对操作相同数据的其他线程立即可见(即它们不被缓存),并且对该变量的操作不会重新排序。不是你真正需要的。 -
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)上,它通常是显著的。