这个C#代码是否会因为寄存器或缓存中的值永远不会写回主内存而失败?
在这篇文章中:
http://msdn.microsoft.com/en-us/magazine/jj883956.aspx
作者声明,由于“循环读取提升”,以下代码可能会失败:
class Test { private bool _flag = true; public void Run() { // Set _flag to false on another thread new Thread(() => { _flag = false; }).Start(); // Poll the _flag field until it is set to false while (_flag) ; // The loop might never terminate! } }
在循环读取提升中,由于单线程假设,编译器可能会将上面的while循环更改为以下内容:
if (_flag) { while (true); }
我想知道的是:如果编译器没有执行该优化,由于一个处理器更新寄存器或缓存中的_flag并且从不将该缓存刷新回到多处理器机器,因此循环仍有可能永远在多处理器机器上运行内存可以被其他线程读取? 我已经读到“C#写入是易失性的”,但是我链接到的文章说ECMA规范实际上并没有保证这一点,并且事情没有在ARM上实现。 我正在试图弄清楚我必须要编写可在所有平台上运行的线程代码。
这是一个相关的问题:
C#线程是否真的可以缓存一个值并忽略其他线程上该值的更改?
但我认为接受的答案中的代码可能会通过循环读取提升进行优化,因此它无法certificate内存可见性……
如果编译器没有执行该优化,由于一个处理器更新寄存器或高速缓存中的_flag并且从不将该高速缓存刷回到另一个线程可读的内存,是否仍有可能在多处理器机器上永远运行该循环?
是。
我已经读到“C#写入是易失性的”,但是我链接到的文章说ECMA规范实际上并没有保证这一点,并且事情没有在ARM上实现。
那有什么关系? 主要线程不是写作,而是阅读。
我正在试图弄清楚我必须要编写可在所有平台上运行的线程代码。
如果他们真的想要你,那不是偏执狂。 线程很难。 做我做的事情:将共享内存的低级操作留给专家。
使用最高级别的抽象编写代码,以及专家为您编写的抽象。 你几乎不应该像你描述的那样编写代码,不是因为它错了 – 尽管它是 – 而是因为它处于错误的抽象层次。 如果你想表达“这个操作可以取消”的想法,那么使用CancellationToken
; 这就是他们的目的。 如果你想表示“这项工作在未来产生结果”的概念,请使用Task
; 这就是他们的目的。 不要试图自己动手; 让微软为你做。
更新:有关C#中的线程安全性,易失性语义,低锁技术以及为什么您应该避免自己完成所有这些操作的更多信息 ,请参阅:
万斯2005年关于低锁技术的精彩文章:
http://msdn.microsoft.com/en-us/magazine/cc163715.aspx
我的2011系列三篇文章从这里开始:
http://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/
特别是第三个与你相关,但前两个也可能很有趣。
Joe Duffy重申了为什么不应该使用volatile:
http://joeduffyblog.com/2010/12/04/sayonara-volatile/
我2014年的一对Ask The Bug Guys文章:
http://blog.coverity.com/2014/03/12/can-skip-lock-reading-integer/ http://blog.coverity.com/2014/03/26/reordering-optimizations/
我按照合理的阅读顺序给了那些; 如果你发现Vance的文章太难了,试着从我的三部曲系列开始,然后再回到它。