.NET:如何确保线程1能够看到线程2在字段中写入的内容?

时间:2010-08-18 13:05:28

标签: .net multithreading synchronization

环境:.NET 3.5 SP1。

我有两个线程:UI线程和后台工作线程。后台工作线程定期更新共享对象中的某些字段,UI线程会检查它们。没什么了不起的 - 只是进步,回报值和抛出异常。工作线程在更改这些字段时会在UI线程上引发一些事件(通过Control.BeginInvoke)。

工作线程只写这些字段,UI线程只读它们。它们不用于任何其他通信。为了性能,我想避免锁定共享对象或单个属性。共享对象中永远不会出现无效状态。

但是我担心处理器缓存和编译器优化等问题。如果在UI线程上的事件处理程序中看不到更新的值,我该如何避免这种情况?将volatile添加到所有字段是否足够?

3 个答案:

答案 0 :(得分:1)

你没事,不用担心。需要内存屏障来清除对内存的任何挂起写入。有一个隐含的任何锁定语句。 Control.Begin / Invoke()需要锁定以保护挂起的委托列表,这样就足够了。

不稳定的要求更难,主要是因为其确切的语义记录很差。在x86 / x64硬件上,它仅阻止JIT编译器缓存CPU寄存器中变量的值。这不是您的问题,因为委托目标指向一个方法。如果未内联方法,则不会跨方法缓存变量。您的代理目标无法内联。

答案 1 :(得分:0)

使用既定的多线程指南要好得多。 {4.0}在.NET 4.0中可用;如果您包含对Producer/consumer collections的引用,这些可用于.NET 3.5。

无锁代码几乎不可能正确。如果您不想使用生产者/消费者队列,请使用锁定。

如果你坚持走下痛苦的道路,那么是的,volatile将允许读取该变量的任何线程获得最后写入的值。

答案 2 :(得分:0)

一般来说,我建议不要使用高级同步机制,因为众所周知难以做到正确,但在这种情况下,可以接受对Thread.MemoryBarrier的调用。当然,假设没有原子性要求,共享对象永远不会处于半生不熟的状态。实际上,这可能比使用volatile洒下所有内容更容易。

object shared;

void WorkerThread()
{
  MakeChangesToSharedObject(shared);
  Thread.MemoryBarrier(); // commit the changes to main memory
}

void UIThread()
{
  Thread.MemoryBarrier(); // ensure updates are read from main memory
  UseSharedObject(shared);
}

也许设计代码以便共享对象是不可变的更好。然后,您所要做的就是在一个简单快速的原子操作中交换共享对象引用。

volatile object shared;

void WorkerThread()
{
  // The following operation is safe because the variable is volatile.
  shared = GetNewSharedObject();
}

void UIThread()
{
  // The following operation is safe because the variable is volatile.
  object value = shared.SomeProperty;
}