C# 何时可以保证一个线程上更改的值对其他线程可见?
我知道线程可以缓存一个值,但我想知道它的一个变体。一个线程是否可能更改一个缓存的值,而该值永远不会离开缓存,因此对其他线程永远都不可见 例如,此代码是否可以打印“Flag is true”,因为线程C# 何时可以保证一个线程上更改的值对其他线程可见?,c#,.net,multithreading,memory-model,memory-barriers,C#,.net,Multithreading,Memory Model,Memory Barriers,我知道线程可以缓存一个值,但我想知道它的一个变体。一个线程是否可能更改一个缓存的值,而该值永远不会离开缓存,因此对其他线程永远都不可见 例如,此代码是否可以打印“Flag is true”,因为线程a从未看到线程b对Flag所做的更改?(我不能让它这样做,但我不能证明它或它的某些变体不会这样做。) 我可以想象,在线程bflag上,它可以缓存在CPU寄存器中,然后设置为false,当b进入而循环时,它永远没有机会(或根本不关心)将flag的值写回内存,因此,a始终将标志视为true 在我看来,从本
a
从未看到线程b
对Flag
所做的更改?(我不能让它这样做,但我不能证明它或它的某些变体不会这样做。)
我可以想象,在线程b
flag
上,它可以缓存在CPU寄存器中,然后设置为false
,当b
进入而循环时,它永远没有机会(或根本不关心)将flag
的值写回内存,因此,a
始终将标志
视为true
在我看来,从本文中列出的记忆屏障发生器来看,理论上是可能的。我说得对吗?我还没能在实践中证明这一点。有人能举一个这样的例子吗?我不完全确定这是否能回答你的问题,但这里是
如果运行以下代码的发行版(非调试版),它将永远不会终止,因为waitForFlag()
从未看到flag
的更改版本
但是,如果您注释掉其中一行,程序将终止
这看起来像是在while(flag)
循环中调用外部库会导致Optimizer不缓存flag
的值
另外(当然)使标志
不稳定将阻止这种优化
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
class Program
{
void run()
{
Task.Factory.StartNew(resetFlagAfter1s);
var waiter = Task.Factory.StartNew(waitForFlag);
waiter.Wait();
Console.WriteLine("Done");
}
void resetFlagAfter1s()
{
Thread.Sleep(1000);
flag = false;
}
void waitForFlag()
{
int x = 0;
while (flag)
{
++x;
// Uncommenting this line make this thread see the changed value of flag.
// Console.WriteLine("Spinning");
}
}
// Uncommenting "volatile" makes waitForFlag() see the changed value of flag.
/*volatile*/ bool flag = true;
static void Main()
{
new Program().run();
}
}
}
研究Thread.MemoryBarrier,你将获得金牌。从最容易正确操作到最容易出错
读/写时使用锁
使用联锁
类上的功能
使用记忆屏障
一个线程是否可能更改一个缓存的值,而该值永远不会离开缓存,因此对其他线程永远都不可见
如果我们说的是硬件缓存,那么我们需要讨论特定的处理器系列。如果您正在x86(和x64)上工作(看起来很可能),那么您需要知道,这些处理器实际上具有比.NET所需的更强的内存模型。在x86系统中,缓存保持一致性,因此其他处理器不能忽略任何写操作
如果我们讨论的是优化,其中一个特定的内存位置已被读入处理器寄存器,然后从内存中进行的后续读取只是重用寄存器,那么在写端就没有类似的模拟。您会注意到,在我们假设没有其他东西在改变该内存位置之前,从实际内存位置至少读取一次,因此我们可以重用寄存器
在写端,我们被告知将某些内容推送到特定的内存位置。我们必须至少推送到那个位置一次,并且总是将以前已知的值存储在一个单独的寄存器中(特别是如果我们的线程从未从中读取),这可能是一种非优化,以便能够执行比较并省略写入操作。@MatthewWatson,谢谢,这个链接回答了我最初提出的问题,但不是我想要找到的。我编辑了这个问题以反映这一点。我实际上是在追踪一个可以用我上面描述的来解释的bug,但是我想知道我的推理是否正确,以及这在现实中的可能性有多大。好的,我现在重新打开了这个问题,它已经被更改了。这个问题可以通过说明所涉及的编程语言来改进。从标签上看,我猜是C#。“我实际上在追踪一个bug…”-如果你能创建一个重新创建这个bug的程序,而不是试图帮助你证明否定,我们可能会更好地帮助你。这说明了我最初链接的问题中发生的情况,也就是说,一个线程缓存一个值,然后错过在另一个线程上对该值进行的更新。我想知道是否可能看到一个线程缓存一个值并修改其缓存的值,但这种变化在其他线程上从未出现过,不管它们是否重新读取该值。这几乎完全回答了这个问题,但我能澄清一些事情吗?“我们必须至少推一次到那个位置”——这个推操作是写操作的一部分吗?i、 这不是可以推迟的事情吗?@eoinmullan-是的,我只是想避免过度使用write。这不是拖延的事情。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
class Program
{
void run()
{
Task.Factory.StartNew(resetFlagAfter1s);
var waiter = Task.Factory.StartNew(waitForFlag);
waiter.Wait();
Console.WriteLine("Done");
}
void resetFlagAfter1s()
{
Thread.Sleep(1000);
flag = false;
}
void waitForFlag()
{
int x = 0;
while (flag)
{
++x;
// Uncommenting this line make this thread see the changed value of flag.
// Console.WriteLine("Spinning");
}
}
// Uncommenting "volatile" makes waitForFlag() see the changed value of flag.
/*volatile*/ bool flag = true;
static void Main()
{
new Program().run();
}
}
}