我尝试了解多线程上下文中volatile的使用。在以下来自Internet上another source of knowledge的代码中:
class Program
{
static string _result;
//static volatile bool _done;
static bool _done;
static void SetVolatile()
{
// Set the string.
_result = "Dot Net Perls";
// The volatile field must be set at the end of this method.
_done = true;
}
static void Main()
{
// Run the above method on a new thread.
new Thread(new ThreadStart(SetVolatile)).Start();
// Wait a while.
Thread.Sleep(200);
// Read the volatile field.
if (_done)
{
Console.WriteLine(_result);
}
}
}
演示使用 volatile 关键字可以防止线程读取存储在缓存中的值。而不是这个,它应该检查实际值。
因此,如果没有volatile _done
仍然应该有一个false值(从缓存中读取),并且不应该执行Console.WriteLine
语句。
不幸的是,在没有volatile关键字的调试/发布模式下运行此代码总会产生输出。这个具体例子有什么意义?
答案 0 :(得分:3)
如前所述,不使用volatile
关键字并不意味着在所有情况下都必须缓存所有读取。它们可能是缓存的,也可能不是。但是如果你想要更多可重复的例子,试试这个:
class Program {
static string _result;
//static volatile bool _done;
static bool _done;
static void SetVolatile() {
// Set the string.
_result = "Dot Net Perls";
// The volatile field must be set at the end of this method.
_done = true;
}
static void Main() {
// Run the above method on a new thread.
new Thread(new ThreadStart(SetVolatile)).Start();
// prevent compiler to throw away empty while loop
// by doing something in it
int i = 0;
while (!_done) {
i++;
}
Console.WriteLine("done " + i);
}
}
在这里,您在while循环中反复阅读_done
,增加了缓存的可能性。程序应以" done"终止。消息但不会,因为从另一个线程更改为_done
将不会被注意到。
答案 1 :(得分:0)
最好阅读ECMA-335,§I.12.6
要点是:
因此,在这种情况下,您的代码可以进行优化。
请尝试以下代码:
private bool flag = true;
public void LoopReadHoistingTest()
{
Task.Run(() => { flag = false; });
while (flag)
{
// Do nothing
}
}
在调试模式下(没有优化)它可以正常工作。在发布模式(带优化)时,它将永久挂起,因为在循环外移动读取是非常常见的优化。
但是,如果您标记字段volatile(或使用Volatile.Read
方法或某些Interlocked
方法),它将起作用,因为在这种情况下禁止优化。
在你的例子中(没有循环),Thread.Sleep
产生一个隐式的内存屏障(因为它不被禁止,它使代码工作的意外更少),所以它将从内存中读取值。但我没有看到任何规范说它必须做一个隐式内存屏障,所以在某些实现中它可能不是真的(或者我们必须在规范中找到它。)