醜いですが、C#コードを使用して、列挙型または64ビット以下の他のブリット可能な値型でアトミックExchangeまたはCompareExchangeを実行することは実際には可能です。unsafe
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ビットunsigned long]
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
が、参照元で使用できないため、これは機能しませんref
。CLRは、 [comment mooted]で動作するプライベート内部関数に関数をマップするため、残念__refvalue
です。JITインターセプトによる、以下を参照])InterlockedExchange
TypedReference
[編集:2018年7月] System.Runtime.CompilerServices。Unsafeライブラリパッケージを使用して、これをより効率的に実行できるようになりました。このメソッドを使用Unsafe.As<TFrom,TTo>()
して、ターゲット管理参照によって参照されるタイプを直接再解釈し、固定とunsafe
モードへの移行の両方の二重の費用を回避できます。
static uint CompareExchange(ref uint target, uint value, uint expected) =>
(uint)Interlocked.CompareExchange(
ref Unsafe.As<uint, int>(ref target),
(int)value,
(int)expected);
static ulong CompareExchange(ref ulong target, ulong value, ulong expected) =>
(ulong)Interlocked.CompareExchange(
ref Unsafe.As<ulong, long>(ref target),
(long)value,
(long)expected);
もちろん、これも同様にInterlocked.Exchange
機能します。4バイトおよび8バイトの符号なしタイプのヘルパーは次のとおりです。
static uint Exchange(ref uint target, uint value) =>
(uint)Interlocked.Exchange(ref Unsafe.As<uint, int>(ref target), (int)value);
static ulong Exchange(ref ulong target, ulong value) =>
(ulong)Interlocked.Exchange(ref Unsafe.As<ulong, long>(ref target), (long)value);
これは列挙型でも機能しますが、基になるプリミティブ整数が正確に4バイトまたは8バイトである場合に限ります。つまり、int
(32ビット)またはlong
(64ビット)サイズです。制限は、これらがオーバーロードの中で見つかった2つのビット幅だけであるということInterlocked.CompareExchange
です。デフォルトでenum
はint
、基になるタイプが指定されていない場合に使用されるため、MyEnum
(上から)正常に機能します。
static MyEnum CompareExchange(ref MyEnum target, MyEnum value, MyEnum expected) =>
(MyEnum)Interlocked.CompareExchange(
ref Unsafe.As<MyEnum, int>(ref target),
(int)value,
(int)expected);
static MyEnum Exchange(ref MyEnum target, MyEnum value) =>
(MyEnum)Interlocked.Exchange(ref Unsafe.As<MyEnum, int>(ref target), (int)value);
最小4バイトが.NETの基本であるかどうかはわかりませんが、私が知る限り、小さい8ビットまたは16ビットのプリミティブ型(、、、、の値)をアトミックに交換する手段はありbyte
ませsbyte
んchar
。ushort
、short
)隣接するバイトへの巻き添え被害のリスクを冒すことなく。次の例では、BadEnum
最大3つの隣接バイトに影響を与えることなく、アトミックにスワップするには小さすぎるサイズを明示的に指定します。
enum BadEnum : byte { }; // can't swap less than 4 bytes on .NET?
相互運用(または固定)レイアウトに制約されない場合、回避策は、アトミックスワッピングを可能にするために、そのような列挙型のメモリレイアウトが常に最小4バイトに埋め込まれるようにすることです(as int
)。ただし、そうすることで、そもそも小さい幅を指定する目的があったとしても、それが無効になる可能性があります。
[編集:2017年4月].NET
私は最近、32ビットモード(またはWOWサブシステム)で実行されている場合、64ビットInterlocked
操作が非「外部」ビューに関してアトミックであることが保証されていないことを知りました。同じメモリ位置。32ビットモードでは、アトミック保証は、 (およびおそらく、または、TBD?)関数を使用するQWORDアクセス全体にのみグロバブルに適用されます。Interlocked
Interlocked
Volatile.*
Thread.Volatile*
言い換えると、32ビットモードで64ビットの不可分操作を取得するには、保証を維持するために、これらのQWORDの場所へのすべてのInterlocked
アクセスが/を介して行われる必要があるため、(たとえば)を直接(つまり、非/ )読み取りは、書き込みに常に/関数を使用するという理由だけで保護されます。Volatile
Interlocked
Volatile
Interlocked
Volatile
Interlocked
最後に、の関数CLR
は.NET JITコンパイラによって特別に認識され、特別に扱われることに注意してください。こことここを参照してくださいこの事実は、私が前述した直感に反することを説明するのに役立つかもしれません。