【问题标题】:Are variables assigned from one thread "thread-safe" when read from another thread if an EventWaitHandel is used?如果使用 EventWaitHandel,从另一个线程读取时从一个线程分配的变量是否“线程安全”?
【发布时间】:2014-02-21 08:56:42
【问题描述】:

如果我在一个线程上创建一个变量,然后使用ManualResetEventWaitOne() 方法阻塞,直到另一个线程将一个值分配给同一变量并发出EventWaitHandel 的信号。当我在第一个线程上读取变量时,我能保证总是得到另一个线程刚刚分配的值吗?

(我担心由于一些优化,我无法从 CPU 缓存中获取值,因为据我所知,我没有使用任何内存屏障)。

例如

var str = "multi-threading is hard!";
var mre = new ManualResetEvent(false);
Task.Factory.StartNew(() => 
    {
        str = Console.ReadLine();
        mre.Set();
    ));
mre.WaitOne();
Console.WriteLine(str);

【问题讨论】:

  • 你说你会使用WaitOne()。在哪里?我希望它在 Console.WriteLine 之前。

标签: c# multithreading .net-4.0 event-wait-handle


【解决方案1】:

这些指令不会被重新排序,这意味着,在生产线程上,字段分配将始终发生在句柄发出信号之前,而在消费线程上,字段将始终是句柄发出信号后读取。

如果这两对指令中的任何一个可以重新排序(例如,如果第二个线程可以在句柄发出信号之前读取字段),那么您将 em> 查看正确的值。

WaitOne() 引入了隐式内存屏障,为您提供所需的获取-释放语义。

Brian Gideon 和 Hans Passant 列出了 .NET 框架中引入隐式内存屏障的几个类:Memory barrier generators

更多信息:Acquire and release semantics / Acquire and release fences

【讨论】:

  • 我没有看到这里重新排序的相关性,但是隐含的内存屏障清除了它!
  • 啊,顺便谢谢你怎么知道指令不能重新排序 - 是因为内存障碍吗?
  • @markmnl 我已经编辑了我的第一段,解释了为什么重新排序是相关的。
  • @markmnl 确实,这就是栅栏/内存屏障的作用。 Acquire-fences 可防止任何存储/负载向上移动,而 release-fences 可防止任何存储/负载移动到栅栏下方。全围栏(例如,Thread.MemoryBarrier)表示全围栏,可防止任何存储/负载向上或向下移动。
【解决方案2】:

您的变量是一个捕获变量,即编译器将此局部变量转换为编译器生成的类的字段,因为您在 lambda 表达式中使用它。 Afaik,这些编译器生成的字段没有标记volatile,所以它们可以被缓存。

编辑:确实,该字段不是易变的。

您绝对可以通过编写自己的类来防止缓存,这样编译器就不必创建一个。但是,这当然会影响代码的简洁性。

【讨论】:

  • 是的,我知道它已被捕获,但如果用我自己的课程重新编写问题,问题仍然存在 - 我可以将其标记为volatile。无论如何,似乎内存障碍是隐含的,所以没有必要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-27
相关资源
最近更新 更多