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有重载。 你需要额外的范围? 如果是这样,尽可能晚地施放/转换。
