是 C# 中的 bool 读/写原子
Posted
技术标签:
【中文标题】是 C# 中的 bool 读/写原子【英文标题】:Is a bool read/write atomic in C# 【发布时间】:2010-09-08 17:37:40 【问题描述】:在 C# 中访问 bool 字段是原子的吗?特别是,我需要加锁吗:
class Foo
private bool _bar;
//... in some function on any thread (or many threads)
_bar = true;
//... same for a read
if (_bar) ...
【问题讨论】:
This earlier question seems related and may have additional useful answers. 是的,但(可能)也是。是的,访问/设置 bool 字段是原子的,但 if 操作不是(请参阅下面 Dror Helper 的答案),因此您可能仍然需要锁。 【参考方案1】:是的。
以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。
在C# Language Spec 中找到。
编辑:了解volatile 关键字可能也值得。
【讨论】:
指针本身,重新分配它,是原子的(即 Foo foo1 = foo2; @configurator:问题是你到底想要什么。无锁程序很容易出错;因此,除非您真的需要它,否则最好使用更简单的框架(例如 TPL)。换句话说,“易失性”并没有错,而是棘手(即最好避免)代码的标志。 OP 并没有真正说出他想要什么,我只是犹豫 recommend volatile willy-nilly。 哇。这是一个危险的措辞,对于 C++ 人来说,原子意味着任何读写也被相应的内存栅栏包围。在 C# 中肯定不是这种情况。因为否则性能会很糟糕,因为它对所有变量都是强制的当读取或写入最终发生时,它们保证永远不会处于损坏状态。但它没有说明什么时候是“最终”。 如果写入 int 和 long 是原子的,那么当使用Interlocked.Add(ref myInt);
例如?
@MikedeKlerk 读取和写入是原子的,但是是分开的。 i++
等于 i=i+1
,这意味着您执行原子读取,然后是加法,然后是原子写入。另一个线程可以在读取之后但在写入之前修改i
。例如,两个线程在同一时间同时执行i++
可能碰巧同时读取(因此读取相同的值),向其添加一个,然后都写入相同的值,实际上只添加一次。 Interlocked.Add 可以防止这种情况。作为一般规则,只有在只有一个线程写入但有多个线程读取时,类型是原子的这一事实才有用。【参考方案2】:
如上所述,bool
是原子的,但您仍然需要记住,它还取决于您想用它做什么。
if(b == false)
//do something
不是原子操作,这意味着b
的值可能在当前线程执行if
语句之后的代码之前发生变化。
【讨论】:
【参考方案3】:bool 访问确实是原子的,但这还不是全部。
您不必担心读取“未完全写入”的值 - 在任何情况下都不清楚这对 bool 可能意味着什么 - 但您至少必须担心处理器缓存如果时间的细节是一个问题。如果在内核 A 上运行的线程 #1 将您的 _bar
缓存在缓存中,并且 _bar
由运行在另一个内核上的线程 #2 更新,则线程 #1 不会立即看到更改,除非您添加锁定,将 _bar
声明为volatile
,或显式插入对 Thread.MemoryBarrier()
的调用以使缓存值无效。
【讨论】:
"在任何情况下都不清楚这对于 bool 可能意味着什么" 在原子时仅存在一个字节内存中的项目,因为同时写入了整个字节。与以多个字节存在的 double 等项目相比,一个字节可以在另一个字节之前写入,您可以观察到一半写入的内存位置。 MemoryBarrier() 不会使任何处理器缓存失效。在某些架构中,允许处理器重新排序对主内存的读取和写入以提高性能。只要从单个线程的角度来看,语义保持不变,就可能发生重新排序。 MemoryBarrier() 请求处理器限制重新排序,以便在屏障之前发出的内存操作不会以最终在屏障之后的方式重新排序。 如果您创建一个胖对象并切换一个可以从其他线程读取的对它的引用,则内存屏障很有用。屏障保证引用不会在胖对象的其余部分之前在主内存中更新。在胖对象在主内存中实际可用之前,保证其他线程永远不会看到引用更新。var fatObject = new FatObject(); Thread.MemoryBarrier(); _sharedRefToFat = fatObject;
【参考方案4】:
我使用的方法,我认为是正确的,是
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;
目标基本上是避免在每次迭代中重复锁定一个对象,只是为了检查我们是否需要锁定它以提供大量很少发生的状态更改信息。我认为这种方法有效。如果需要绝对一致性,我认为 volatile 适合 b 布尔值。
【讨论】:
这确实是一般锁定的正确方法,但如果 bool 是原子的,那么省略锁定会更简单(也更快)。 如果没有锁,那么“大状态更改”将不会自动完成。锁 -> 设置 | check -> lock -> check 方法还将确保“//other”代码在“//other stuff”代码之前执行。假设“另一个线程”部分迭代了很多次(在我的情况下),大多数时候只需要检查一个布尔值,但实际上并没有获得一个(可能是竞争的)锁,这是一个主要的性能胜利跨度> 好吧,如果你有lock()
,你就不需要volatile
。以上是关于是 C# 中的 bool 读/写原子的主要内容,如果未能解决你的问题,请参考以下文章