C# 当工作线程以非竞争性方式写入本地或类变量时,是否需要锁或易失性?

C# 当工作线程以非竞争性方式写入本地或类变量时,是否需要锁或易失性?,c#,variables,locking,task,volatile,C#,Variables,Locking,Task,Volatile,对于下面的情况,当工作线程之间没有写入竞争时,是否仍然需要锁或volatile?如果在“G”处不需要“Peek”访问,则答案有任何差异 写入引用类型(即Object)和字大小的值类型(即32位系统中的int)是原子的。这意味着,当您查看值(位置6)时,您可以确保您得到的是旧值或新值,而不是其他值(如果您有一个类型,如大型结构,则可以将其拼接,并且可以在写入一半时读取该值)。只要您愿意接受读取过时值的潜在风险,就不需要锁或易失性 请注意,因为此时没有引入内存障碍(一个锁或使用volatile都添加

对于下面的情况,当工作线程之间没有写入竞争时,是否仍然需要锁或volatile?如果在“G”处不需要“Peek”访问,则答案有任何差异

写入引用类型(即
Object
)和字大小的值类型(即32位系统中的
int
)是原子的。这意味着,当您查看值(位置6)时,您可以确保您得到的是旧值或新值,而不是其他值(如果您有一个类型,如大型结构,则可以将其拼接,并且可以在写入一半时读取该值)。只要您愿意接受读取过时值的潜在风险,就不需要
易失性

请注意,因为此时没有引入内存障碍(一个
或使用
volatile
都添加一个),所以变量可能已在另一个线程中更新,但当前线程没有观察到该更改;它可以在另一个线程中更改“过时”值后(可能)相当长的一段时间内读取该值。使用
volatile
将确保当前线程能够更快地观察到变量的更改

您可以确保在调用
WaitAll
后,即使没有
volatile
,也会得到适当的值

还要注意的是,虽然您可以确保引用类型的引用是以原子方式编写的,但您的程序并不保证引用所引用的实际对象的任何更改的观察顺序。即使从后台线程的角度来看,对象在分配给实例字段之前已初始化,它也可能不会按该顺序发生。因此,另一个线程可以观察引用对对象的写入,但随后跟随该引用并找到处于初始化或部分初始化状态的对象。引入内存屏障(即通过使用
volatile
变量),可以潜在地阻止运行时进行此类重新排序,从而确保不会发生这种情况。这就是为什么这两个任务只返回它们生成的结果,而不是操纵封闭变量


WaitAll
除了确保两个任务实际完成外,还将引入一个内存屏障,这意味着您知道变量是最新的,不会有旧的过时值。

在位置G,您可以观察值
\u o
\u i
可以保留其初始化值为空d 0,或者它们可能包含任务写入的值。此位置不可预测

但是,在位置H处,您以两种不同的方式强制执行该问题。首先,您已保证两个任务都已完成,因此写入操作已完成。其次,
Task.WaitAll
将生成一个内存屏障,该屏障将保证主线程将观察任务发布的新值


因此,在这个特定示例中,显式锁或内存屏障生成器(
volatile
)技术上不需要。

安全的做法是首先不要这样做。不要先在一个线程上写入值,然后在另一个线程上读取值。创建一个
任务和一个
任务,将值返回到需要它们的线程,而不是创建跨线程修改变量的任务美国

如果您执意要跨线程写入变量,那么您需要保证两件事。首先,抖动不会选择导致读写在时间上移动的优化,其次,引入了内存屏障。内存屏障限制处理器在时间上移动读写在某些方面

正如Brian Gideon在他的回答中所指出的,您从
WaitAll
中获得了一个内存障碍,但我不记得这是一个文档化的保证还是一个实现细节


正如我所说,我一开始不会这样做。如果我被迫这样做,我至少会将我编写的变量标记为volatile。

Servy的回答是正确的,但是要注意你在位置G的期望,因为你可能会得到一些非直观的结果。例如,你可能
o
分配给新对象,但是
\u o
仍然为空,尽管它们应该以相反的顺序发生。此外,如果“偷看”背后的意图就是在所有其他任务都完成之前处理一个已完成任务的结果,那么正确的方法是用一个
WaitAny
调用循环替换
WaitAny
。@EricLippert:我不同意。如果考虑
int
vs
long
32位机器上的赋值。@EricLippert位于位置G(以前版本中为6)除了未完成的任务之外,没有内存障碍。即使没有内存障碍,也可以选择这两个值中的任何一个。在
WaitAll
之后,您可以确保所有读取都会产生正确的值,因为它引入了内存障碍。因此,您可以断言答案是insuff冷冰冰地解释(因为我没有解释为什么我的最后一句话是真的),但我看不出有什么不正确的地方?@Douglas:你的不同意并不能改变事实。这里重要的是,处理器和抖动不能进行优化,这会导致从变量中读取过时的值。错误值的原子读取仍然是错误值的读取。@Servy:好的,我看到你的poi现在,我收回我的反对意见。当你说“你
class A 
{
   Object _o; // need volatile (position A)?
   Int _i;    // need volatile (position B)?

   Method()
   {
      Object o;
      Int i;

      Task [] task = new Task[2]
      {
         Task.Factory.StartNew(() => { 
              _o = f1();   // use lock() (position C)?
              o  = f2();   // use lock() (position D)?
         } 
         Task.Factory.StartNew(() => { 
              _i = g1();   // use lock() (position E)?
              i  = g2();   // use lock() (position F)?
         }          
      }

      // "Peek" at _o, _i, o, i (position G)?

      Task.WaitAll(tasks);

      // Use _o, _i, o, i (position H)?
}