是C#中的bool读/写原子


84

在C#中访问布尔字段原子吗?特别是,我是否需要锁定:

class Foo
{
   private bool _bar;

   //... in some function on any thread (or many threads)
   _bar = true;

   //... same for a read
   if (_bar) { ... }
}


1
是的,但是(可能)也是。是的,访问/设置布尔字段是原子的,但是if操作不是(请参见下面的Dror Helper的答案),因此您仍然需要锁定。
JPProgrammer

Answers:


119

是。

以下数据类型的读取和写入是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和引用类型。

C#语言规范中所述

编辑:这也许也值得理解volatile关键字。


10
重新分配指针本身是原子的(即Foo foo1 = foo2;
2009年

4
@configurator:问题是您到底想要什么。无锁程序容易出错。因此,除非您真的需要它,否则最好使用更简单的框架(例如TPL)。换句话说,“易失性”不是错,而是棘手(即最好避免)代码的标志。OP并没有真正说出他想要什么,我只是在犹豫不决地推荐动荡的Willy-nilly。
Eamon Nerbonne

4
哇 对于C ++人士来说,这是一个危险的措辞,原子意味着任何读写操作也都被相应的内存保护区包围。在C#中肯定不是这种情况。因为否则对于所有变量<long都是强制性的,因此性能将很糟糕。用C#来说,这里的原子似乎比读或写最终发生时要确保它们永远不会处于损坏状态。但是它并没有说明何时“最终”。
v.oddou 2015年

4
如果写入int和long是原子的,那么何时使用Interlocked.Add(ref myInt);例如?
Mike de Klerk 2015年

6
@MikedeKlerk读取和写入是原子的,但是是分开的。i++等于i=i+1,表示您先进行原子读取,然后进行加法,然后进行原子写入。另一个线程可以i在读取之后但在写入之前进行修改。例如i++,在同一线程上同时执行的两个线程可能碰巧同时读取(并因此读取相同的值),向其添加一个,然后都写入相同的值,实际上仅添加一次。Interlocked.Add可以防止这种情况。通常,仅当写入一个线程但读取多个线程时,类型为原子的事实才有用。
Jannis Froese

49

如上所述,布尔是原子的,但是您仍然需要记住,布尔也取决于您要如何处理。

if(b == false)
{
    //do something
}

不是原子操作,这意味着b值可能会在当前线程执行if语句之后的代码之前更改。


29

布尔访问确实是原子的,但这还不是全部。

您不必担心读取“未完全写入”的值-尚不清楚在任何情况下对bool可能意味着什么-但您必须担心处理器缓存,至少在以下情况下需要担心:时间是一个问题。如果在内核A上运行的线程#1具有您的_barin缓存,并且_bar被在另一个内核上运行的线程#2更新,则线程#1将不会立即看到更改,除非您添加锁定,声明_barvolatile或显式插入Thread.MemoryBarrier()来使无效。缓存的值。


1
“在任何情况下,对于bool都可能意味着什么尚不清楚”原子内存中仅存在一个字节中的项,因为整个字节是同时写入的。与像double这样的项目存在多个字节中一样,可以在另一个字节之前写入一个字节,然后可以观察到一半写入的存储器位置。
MindStalker 2010年

3
MemoryBarrier()不会使任何处理器缓存无效。在某些体系结构中,允许处理器对主存储器的读取和写入进行重新排序以提高性能。只要从单个线程的角度来看,重新排序就可能发生,语义保持不变。MemoryBarrier()请求处理器限制重新排序,以便在屏障之前发出的内存操作不会以在屏障之后结束的方式重新排序。
TiMoch 2013年

1
如果创建胖对象并切换到可能从其他线程读取的引用,则内存屏障很有用。屏障确保引用不会在其余胖对象之前在主内存中更新。保证其他线程永远不会在胖对象实际在主内存中可用之前看到引用更新。var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
TiMoch 2013年

1

我使用的方法(我认为是正确的)是

volatile bool b = false;

.. rarely signal an update with a large state change...

lock b_lock
{
  b = true;
  //other;
}

... another thread ...

if(b)
{
    lock b_lock
    {
       if(b)
       {
           //other stuff
           b = false;
       }
     }
}

目标基本上是避免每次迭代都必须重复锁定对象,而只是为了提供大量很少发生的状态改变信息而检查是否需要锁定它。我认为这种方法行得通。而且,如果需要绝对一致性,我认为在bool上使用volatile较为合适。


4
通常,这确实是一种正确的锁定方法,但是如果布尔值是原子的,则省略锁定更为简单(且速度更快)。
dbkk

2
如果没有锁,那么“大状态更改”将不会自动完成。锁->设置| check-> lock-> check方法还将确保在“ // other stuff”代码之前执行“ // other”代码。假设“另一个线程”部分进行了多次迭代(在我的情况下),大多数时候只需要检查布尔值,而实际上并不需要获得(可能是竞争的)锁,则是主要的性能优势
stux

1
好吧,如果有lock(),就不需要volatile
xmedeko
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.