C#联锁交换
我有一些看起来像这样的游戏:
public static float Time;
float someValue = 123;
Interlocked.Exchange(ref Time, someValue);
我想改变时间是一个Uint32; 但是,当我尝试使用UInt32
而不是float
作为值时,它会抗议该类型必须是引用类型。 Float
不是一个引用类型,所以我知道在非引用类型中做到这一点在技术上是可行的。 有什么实际的方法来使这个UInt32
工作?
虽然丑陋,但实际上可以使用unsafe
C#代码对枚举或其他可位值为64位或更小的值类型执行原子Exchange或CompareExchange :
enum MyEnum { A, B, C };
MyEnum m_e = MyEnum.B;
unsafe void example()
{
MyEnum e = m_e;
fixed (MyEnum* ps = &m_e)
if (Interlocked.CompareExchange(ref *(int*)ps, (int)(e | MyEnum.C), (int)e) == (int)e)
{
/// change accepted, m_e == B | C
}
else
{
/// change rejected
}
}
违反直觉的部分是解引用指针上的ref表达确实贯穿到枚举的地址。 我认为编译器应该有权在堆栈上生成一个不可见的临时变量,在这种情况下,这是行不通的。 使用风险自负。
[编辑:对于OP所要求的特定类型]
static unsafe uint CompareExchange(ref uint target, uint v, uint cmp)
{
fixed (uint* p = &target)
return (uint)Interlocked.CompareExchange(ref *(int*)p, (int)v, (int)cmp);
}
[编辑:和64位无符号长]
static unsafe ulong CompareExchange(ref ulong target, ulong v, ulong cmp)
{
fixed (ulong* p = &target)
return (ulong)Interlocked.CompareExchange(ref *(long*)p, (long)v, (long)cmp);
}
(我也尝试使用未公开的C#关键字__makeref
来实现这个目的,但是这样做不起作用,因为你不能在dreferenced __refvalue
上使用ref
,这太糟糕了,因为CLR将InterlockedExchange
函数映射到一个专用的内部函数on TypedReference
[由JIT拦截提出的评论,见下文])
[编辑:2017年4月]我最近了解到,当.NET
运行在32位模式下(或者,在WOW子系统中)时,64位Interlocked
操作无法保证在非 Interlocked
是原子的,“外部“视图相同的内存位置。 在32位模式下,原子保证仅适用于使用Interlocked
(也许是Volatile.*
或Thread.Volatile*
,TBD?)函数的QWORD访问中的Thread.Volatile*
变量。
换句话说,要在32位模式下获得64位原子操作, 所有对QWORD位置的访问必须通过Interlocked
进行以保证保证,并且假设(例如)直接读取受到保护,您就不会变得可爱因为您总是使用Interlocked
功能进行书写。
最后,请注意CLR
中的Interlocked
函数被.NET JIT编译器特别识别并受到特殊处理。 看到这里和这里这个事实可能有助于解释我前面提到的反直觉。
Interlocked.Exchange
专门用于float
(以及其他用于double
, int
, long
, IntPtr
和object
)的重载。 对于uint没有一个,所以编译器认为最接近的匹配是通用的Interlocked.Exchange<T>
- 但在这种情况下, T
必须是引用类型。 uint
不是引用类型,因此也不起作用 - 因此是错误消息。
换一种说法:
Interlocked.Exchange(ref float, float)
。 uint
失败,因为没有适用的超载。 确切的错误信息是由编译器猜测你的意思是Interlocked.Exchange<T>(ref T, T)
。 至于做什么,选项是以下任何一种:
int
来代替。 long
。 uint
但不要尝试写无锁代码 虽然很显然Exchange
可以很好地处理某些特定的值类型,但是Microsoft并没有为所有的基本类型实现它。 我无法想象要做到这一点很困难(毕竟他们只是一点点),但可能他们想要保持超负荷计数。
也许使用int
而不是uint
; int
有重载。 你需要额外的范围? 如果是这样,尽可能晚地施放/转换。