环境:.NET 3.5 SP1。
我有两个线程:UI线程和后台工作线程。后台工作线程定期更新共享对象中的某些字段,UI线程会检查它们。没什么了不起的 - 只是进步,回报值和抛出异常。工作线程在更改这些字段时会在UI线程上引发一些事件(通过Control.BeginInvoke
)。
工作线程只写这些字段,UI线程只读它们。它们不用于任何其他通信。为了性能,我想避免锁定共享对象或单个属性。共享对象中永远不会出现无效状态。
但是我担心处理器缓存和编译器优化等问题。如果在UI线程上的事件处理程序中看不到更新的值,我该如何避免这种情况?将volatile
添加到所有字段是否足够?
答案 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;
}