如果我调用Thread.Join(),那我需要使用volatile吗?

5
在Java中,如果您在加入突变线程后仅访问字段,则不需要使其易失性;加入强制执行先于关系。
那么在C#中呢?通过以下代码,调用Join()后是否保证看到更新的_value值,还是我需要将_value设置为易失性?
private String _value = "FOO"

public void Foo() 
{

   Thread myThread = new Thread(Bar);
   myThread.Start();
   myThread.Join();
   Console.WriteLine("[Main Thread] _val = "+ _value);

}

public void Bar()
{

   for(int i=0; i<1000; i++)
   {
         Console.WriteLine("Looping");

         if(i==75) 
         {
             _value="BAR";
         }
   }
   Console.WriteLine("DONE Looping");
}

在我的代码片段中,"BAR" 总是会被打印出来吗?

2
@MatthewWatson:你对volatile的理解有误。这是关于CPU缓存的问题。而且我不确定它是否安全。 - SLaks
2
@MatthewWatson:你需要阅读https://blogs.msdn.microsoft.com/ericlippert/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/。 - SLaks
2
除了“普遍认可”的来源外,没有其他的。您可以在CoreCLR存储库中提交问题以请求明确的保证。 - SLaks
1
@JimMischel 我不会这么说。与 C 不同,C# 中的 volatile 具有明确定义和有用的含义。它是一种内存访问加上一个屏障。同时通过 class Volatile 暴露出来。 - usr
2
@usr:你这么说好像明确定义的意义会产生易于人类理解的行为。我对于是否有人真正理解volatile的标准测试 - 我自己并不算在这个群体中 - 是看看他们是否能够解决这个谜题:http://blog.coverity.com/2014/03/26/reordering-optimizations/ - Eric Lippert
显示剩余11条评论
2个回答

3
首先:我的一个通用的经验法则是,如果我在问“这需要是volatile吗?”那么我对内存模型的理解还不足以编写低锁代码。
只需将该对象放置在锁下,并且不要尝试编写低锁代码,除非有一个非常好的理由和专家的建议。
我不是这样的专家。 我对C#内存模型的了解远远不够,不能自信地编写低锁代码,以确保在弱内存模型硬件上运行时正确性。
回答您实际的问题:

在调用Join()后,我是否保证看到_value的更新值,或者我需要使_value变成volatile?

您的问题的答案在C#规范中,我在此引用:

C#程序的执行遵循这样的方式,即在关键执行点处保留每个执行线程的副作用。副作用定义为易失性字段的读取或写入,对非易失性变量的写入,对外部资源的写入以及抛出异常。必须在这些副作用的顺序被保留的关键执行点是易失性字段的引用,锁语句以及线程的创建和终止。

您对一个非易失性变量进行了写入,并且在线程结束时调用了join,因此必须在join点处保留写入的副作用。

不错,这总算是有规定的了。虽然其他常见原语仍有许多未解之谜(与本问题无关)。该规范中的列表只是杯水车薪。 - usr

1

常见的线程同步操作会执行完整的内存屏障。启动和加入线程以及结束线程肯定是其中之一。如果没有这个,各种程序都会出现故障。

这些保证通常没有记录,但实际上是显而易见的。遗憾的是,我无法提供硬性证据,除了说其他任何东西都是疯狂的。

请参见this list作为证据,说明这并没有被很好地记录下来,并且您正在寻找的属性很可能成立。

在我的代码片段中,"BAR"是否总是会被打印出来?

是的。我相信所有专家都会同意这一点。下面是一个更简单的代码示例,也说明了同样的问题:

int x = 0;
Thread myThread = new Thread(() => x = 1);
myThread.Start();
myThread.Join();
x = 2;
Console.WriteLine(x); //Prints 2 because of memory barriers on exit and on Join.

它显示输出字符串的顺序。控制台是同步的。因此,这是全球排序的好测试。如果您更喜欢,可以使用全局变量。@sstan - usr
假设使用join发出了障碍,那么如何打印2,1呢?(不可能打印)。 - usr
请注意,这是可以保证正常工作的,因为 Console.WriteLine 是对“外部资源”的写操作。 - Eric Lippert
@EricLippert对。不再使用控制台写入了。这段代码片段没有展示重点。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接