我什么时候可以保证在一个线程上更改的值对其他线程可见?

时间:2016-06-17 10:46:54

标签: c# .net multithreading memory-model memory-barriers

我理解一个线程可以缓存一个值ignore changes made on another thread,但我想知道这个变种。线程是否有可能更改缓存值,然后永远不会离开其缓存,因此其他线程永远不可见?

例如,此代码是否可以打印“Flag is true”,因为线程a从未看到线程bflag所做的更改? (我不能这样做,但我不能证明它,或者它的一些变化,不会。)

var flag = true;
var a = new Thread(() => {
    Thread.Sleep(200);
    Console.WriteLine($"Flag is {flag}");
});
var b = new Thread(() => {
    flag = false;
    while (true) {
        // Do something to avoid a memory barrier
    }
});

a.Start();
b.Start();

a.Join();

我可以想象,在线程b flag上可以缓存在CPU寄存器中,然后将其设置为false,当b进入{{1}时循环永远不会有机会(或从不关心)将while的值写回内存,因此flag始终将a视为真。

this answer中列出的内存屏障生成器来看,这对我来说似乎是可能的。我对么?我无法在实践中证明这一点。任何人都可以提出一个例子吗?

4 个答案:

答案 0 :(得分:1)

  

线程是否有可能更改缓存值,然后永远不会离开其缓存,因此其他线程永远不可见?

如果我们正在谈论硬件缓存,那么我们需要讨论特定的处理器系列。如果你在x86(和x64)上工作(似乎很可能),你需要知道那些处理器实际上具有比.NET所需的更强大的内存模型。在x86系统中,缓存保持一致性,因此其他处理器不能忽略任何写入。

如果我们谈论的是优化,其中特定的存储器位置已被读入处理器寄存器,然后后续从存储器读取只是重用寄存器,那么就没有类似的模拟在写方面。您会注意到,在我们假设没有其他任何内容正在改变该内存位置之前,始终至少有一个从实际内存位置读取,因此我们可以重用该寄存器。

在写入方面,我们被告知要将某些内容推送到特定的内存位置。我们必须至少推送到该位置一次,并且在单独的寄存器中始终将先前已知的值存储在该位置(特别是如果我们的线程从不从它读取)可能是一种去优化,以便能够执行比较并且忽略了写操作。

答案 1 :(得分:0)

我不完全确定这会回答你的问题,但现在就去了。

如果您运行以下代码的发行版(非调试版),它将永远不会终止,因为永远不会看到waitForFlag()的更改版本。

但是,如果您注释掉任何指定的行,程序终止。

看起来在flag循环中调用外部库会导致优化器不缓存while (flag)的值。

同样(当然)使flag不稳定会阻止这种优化。

flag

答案 2 :(得分:0)

研究Thread.MemoryBarrier,你将是金色的。

答案 3 :(得分:0)

从最简单到正确到最容易搞定

  1. 读/写时使用锁
  2. 使用<select name="city"> <option value="Los Angeles">Los Angeles</option> // Other California cities </select>
  3. 上的功能
  4. 使用记忆障碍