正如标题所说,在读/写操作中被认为是可以为空的布尔(bool?)?我通过C#文档搜索无济于事。我知道只有某些原始类型在执行读/写操作时保证原子性,而Bool是这些类型中的一种。当然,可空的布尔是一个不同的故事;毕竟他们是对象,所以我会说不,但......有人可以对这个问题有所了解吗?
答案 0 :(得分:5)
C#不保证可空变量的读写是原子的。保证操作为原子的类型在规范的第5.5节(变量引用的原子性)中定义:
以下数据类型的读写是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference类型。此外,在先前列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读写,包括long,ulong,double和decimal,以及用户定义的类型,不保证是原子的。除了为此目的而设计的库函数之外,不保证原子读 - 修改 - 写,例如在递增或递减的情况下。
请注意,虽然nullables可以为null,但它们不是引用类型。它们是具有运行时提供的特殊装箱行为的值类型。在规范的上下文中,如果需要特殊处理,它们被称为可空值类型。
答案 1 :(得分:3)
根据Nullable(T):
此类型的任何公共静态成员都是线程安全的。不保证任何实例成员都是线程安全的。
答案 2 :(得分:1)
请注意,bool?
实际上是一个Nullable<bool>
;换句话说,它是一个结构。有一些特殊的运行时功能使得这些功能就好像它们可以为装箱之类的东西一样,但最终它是一个带有HasValue
标志和实际值的结构。
这种情况非常好,因为它不提供原子读/写保证。从理论上讲,它可以在bool?
完成,因为你可以将所有必要的数据放入一个机器字中,但对于一般可为空的实例而言,实际上不可能在没有烘焙的情况下围绕可空访问进行某些同步。
另一方面,如果您需要这样的东西,请考虑bool?
基本上只是一个三态值。您可以使用具有三种状态的Enum来完成相同的结果,这确实提供了原子读/写保证(因为它只是默认情况下的一个int。)虽然通常,使用同步原语而不是尝试更安全正确实现无锁代码。
答案 3 :(得分:-1)
给出我对Nullable<bool>
属性的实现:
// defaultValue: 0 = false, 1 = true, 2 = null
private int _threadSafeNullBool = 2;
public bool? ThreadSafeNullBool
{
get
{
// check for value with noop (if value is 2, set to 2)
switch (Interlocked.CompareExchange(ref _threadSafeNullBool, 2, 2))
{
case 2:
return null;
case 1:
return true;
default:
return false;
}
}
set
{
// setting null
if (!value.HasValue)
{
// if value is 0, set 2
if (Interlocked.CompareExchange(ref _threadSafeNullBool, 2, 0) == 1) // if value is 1
{
// set 2
Interlocked.Exchange(ref _threadSafeNullBool, 2);
}
}
// setting true
else if (value.Value)
{
// if value is 0, set 1
if (Interlocked.CompareExchange(ref _threadSafeNullBool, 1, 0) == 2) // if value is 2
{
// set 1
Interlocked.Exchange(ref _threadSafeNullBool, 1);
}
}
// setting false
else
{
// if value is 1, set 0
if (Interlocked.CompareExchange(ref _threadSafeNullBool, 0, 1) == 2) // if value is 2
{
// set 0
Interlocked.Exchange(ref _threadSafeNullBool, 0);
}
}
}
}
我将_threadSafeNullBool
用作具有3种状态(0 =否,1 =正确,2 =空)的后备字段。
在Nullable<bool>
属性的获取器中,我只是在获取变量值并将其与状态进行比较,并根据值进行重现。我正在使用Interlocked.CompareExchange
函数,因为它读取变量的最新值(而不是当前线程缓存的那个值)。
在设置器中,每种可能的状态都有相似的代码。对于设置null
,如果当前值为Interlocked.CompareExchange
(方法的第三个参数),我首先使用2
将变量设置为值0
。
该方法正在返回原始值(可能将其设置为2
之前),并且我将此值与1
(可能的状态,CompareExchange
本身未捕获)进行比较。如果比较成功(值为1
),则使用方法2
将其设置为值Interlocked.Exchange
。
相似的代码用于value
中的每个可能状态。仅更改值。
答案 4 :(得分:-2)
理论上:就像你说的那样,没有保证。
实际上:将它从一个变量复制到另一个变量是线程安全的。不必阅读和写作。
这意味着:
shared = true; //avoid this
bool result = shared.Value; //avoid this, too
bool? local = true;
shared = local; //threadsafe in practice
[...]
local = shared; //threadsafe in practice
原因是,如果没有进行类型转换,那么运算符会在一个步骤中将指针大小或较小的结构体整体复制。
使值有用需要两个步骤:1。检查标志是否为空2.阅读实际内容。这意味着它可能不是线程安全的。
最好使用带有常量的int
为true,false和null。这也允许您使用Interlocked功能(Exchange和CompareExchange可以非常方便!)