.NET:如何确保线程1可以看到线程2在字段中写入的内容?
环境:.NET 3.5 SP1。
我有两个线程:UI 线程和后台工作线程。后台工作线程定期更新共享对象中的某些字段,并且 UI 线程检查它们。没什么了不起的——只是进度、返回值和抛出的异常。当工作线程更改这些字段时,它还会在 UI 线程上引发一些事件(通过 Control.BeginInvoke)。
工作线程仅写入这些字段,而 UI 线程仅读取它们。它们不用于任何其他通信。为了提高性能,我想避免锁定共享对象或单个属性。共享对象中永远不会存在无效状态。
然而,我担心处理器缓存和编译器优化等问题。如何避免更新值在 UI 线程的事件处理程序中不可见的情况?向所有字段添加 易失性
就足够了吗?
Environment: .NET 3.5 SP1.
I've got two threads: UI thread and a background worker thread. The background worker thread periodically updates some fields in a shared object and the UI thread checks them. Nothing spectacular - just the progress, return values and thrown exceptions. Also the worker thread raises some events on the UI thread (via Control.BeginInvoke
) when it changes these fields.
The worker thread ONLY WRITES these fields, and the UI thread ONLY READS them. They are not used for any other communication. For the sake of performance I'd like to avoid locking on the shared object or the individual properties. There will never be an invalid state in the shared object.
However I'm worried about things like processor caches and compiler optimizations. How can I avoid the situation when an updated value is not visible in the event handler on the UI thread? Will adding volatile
to all fields be enough?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
你没事,不用担心。需要内存屏障来刷新对内存的任何挂起的写入。任何锁定语句都有一个隐式锁定。 Control.Begin/Invoke() 需要锁定来保护待处理委托列表,这样就足够了。
易失性要求是一项更难的要求,主要是因为其确切语义的记录很少。在 x86/x64 硬件上,它只是阻止 JIT 编译器在 CPU 寄存器中缓存变量的值。对于您的情况,这不是问题,因为委托目标指向一个方法。如果方法未内联,则变量不会跨方法缓存。您的委托目标无法内联。
You're okay, no need to worry. A memory barrier is required to flush any pending writes to memory. There's an implicit one with any lock statement. Control.Begin/Invoke() needs to take a lock to protect the list of pending delegates so that's sufficient.
The volatile requirement is a harder one, mostly because its exact semantics are so poorly documented. On x86/x64 hardware it merely prevents the JIT compiler from caching the value of a variable in a CPU register. This is not an issue in your case because the delegate target points to a method. Variables are not cached across methods if the methods are not inlined. Your delegate target cannot be inlined.
使用既定的多线程指南要好得多。 生产者/消费者集合在 .NET 4.0 中可用;如果您包含对 Rx 库。
无锁代码几乎不可能正确。如果你不想使用生产者/消费者队列,那么就使用锁。
如果您坚持沿着痛苦的道路走下去,那么是的,易失性将使任何读取该变量的线程都能获取最后写入的值。
It is far, far better to use established multithreading guidelines. Producer/consumer collections are available in .NET 4.0; these are also available for .NET 3.5 if you include references to the Rx library.
Lock-free code is almost impossible to get right. If you don't want to use the producer/consumer queue, then use a lock.
If you insist on going down the path of pain, then yes,
volatile
will enable any thread reading that variable to get the last written value.一般来说,我建议不要使用高级同步机制,因为它们非常难以正确使用,但在这种情况下,对 Thread.MemoryBarrier 的调用是可以接受的。当然,这是假设没有原子性要求,并且共享对象永远不会处于半生不熟的状态。实际上,这可能比在所有内容上撒上
易失性
更容易。也许设计代码使共享对象不可变会更好。然后您所要做的就是通过一个简单而快速的原子操作交换共享对象引用。
Generall speaking I advise against using advanced synchronization mechanisms because they are notoriously difficult to get right, but in this case a call to
Thread.MemoryBarrier
could be acceptable. Of course that assumes there are no atomicity requirements and the shared object can never be in a half-baked state. This might actually be easier than sprinkling everything withvolatile
.Perhaps designing the code so that the shared object is immutable would be better though. Then all you have to do is swap out the shared object reference in one simple and quick atomic operation.