【问题标题】:Array of objects, updating in one thread and reading in another对象数组,在一个线程中更新并在另一个线程中读取
【发布时间】:2011-12-18 11:00:15
【问题描述】:

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

我有两个线程,分别称为AB。它们共享一个Foo 类型的对象,该对象有一个名为Name 的字段,并存储在索引0Foo[] 类型数组中。线程将始终按照系统已保证的顺序访问索引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 处的对象中的所有字段也获得它们的最新值?

【问题讨论】:

  • 你为什么使用数组?查看System.Collections.Concurrent 命名空间。
  • 首先不使用 .NET4。据我所知,并发的东西只保证集合本身的内容,而不是集合中对象的可能字段/属性。
  • 我不认为 volatile 能保证你所想的。
  • 如果不能保证插入到数组中的对象是线程安全的,我认为你必须在系统中进行某种检查。我可能会通过使用 Concurrent 命名空间中的集合之一来实现它。创建一个具有公共 T 值的 IDisposable 和泛型新类型。每当一个线程处理完该项目时,它都会处理它,此时您将允许其他线程读取它。只是一个想法。
  • 另一个想法是只允许线程安全项进入自定义集合。您只需在 List 或 T[] 周围放置一个通用包装器,并限制 T 是 IThreadSafe(它没有任何成员,但作为对您和其他开发人员的提醒)。

标签: c# .net multithreading mono thread-safety


【解决方案1】:

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 变量。

编辑:好的,所以您确定上述流程得到尊重。既然您说您不能声明字段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

【讨论】:

  • 这不是问题,两个线程不能同时访问。曾经。我认为人们误解了我的问题,我需要重写它。
  • 访问将始终是顺序的,但来自两个(或可能更多)不同的线程。
  • @thr:从这个声明:“现在能够保证 Thread1 获取存储在 Foo 的 Name 字段中的新值......”我知道您需要 Thread1 仅在之后读取 Name Thread0 已完成更新。
  • 是的,我有点认为人们误解了我。 Thread1 总是会在 Thread0 写入之后访问 .Name 的值,这是系统已经保证的。我担心的是 Thread1 被保证获得 Thread0 写入的值。没有竞争条件。
  • @thr:那么您所需要的只是易失性。请参阅我在回答的第 1 点中写的内容。我会进行编辑。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-16
    • 2011-11-09
    • 2016-10-06
    相关资源
    最近更新 更多