9

私はこのように見える私のゲームのビットを持っています:

public static float Time;

float someValue = 123;
Interlocked.Exchange(ref Time, someValue);

TimeをUint32に変更したい。ただし、値UInt32の代わりに使用しようとするfloatと、型は参照型でなければならないことに抗議します。Floatは参照型ではないので、非参照型でこれを行うことは技術的に可能であると私は知っています。これを機能させるための実用的な方法はありますUInt32か?

4

6 に答える 6

19

Interlocked.Exchange特にfloat(および、、、、および)doubleにはオーバーロードがあります。uintには1つがないため、コンパイラは最も近い一致がジェネリックであると見なしますが、その場合は参照型である必要があります。は参照型ではないため、これも機能しません。したがって、エラーメッセージが表示されます。intlongIntPtrobjectInterlocked.Exchange<T>Tuint

言い換えると:

  • を呼び出すため、現在のコードは機能しますInterlocked.Exchange(ref float, float)
  • 適用可能な過負荷がないため、これをに変更するとuint失敗します。正確なエラーメッセージは、コンパイラがあなたが意味していると推測することによって発生しますInterlocked.Exchange<T>(ref T, T)

何をすべきかについては、オプションは次のいずれかです。

  • intマークが示唆するように、代わりに使用する可能性があります。
  • 追加の範囲が必要な場合は、の使用を検討してlongください。
  • ロックフリーコードを使用しますuintが、書き込もうとしないでください

明らかにいくつかのExchange特定の値型で正常に動作しますが、Microsoftはすべてのプリミティブ型にそれを実装していません。そうするのは難しいとは思いませんが(結局のところ、ほんの少しです)、おそらく彼らは過負荷のカウントダウンを維持したかったのでしょう。

于 2009-07-12T20:12:24.773 に答える
18

醜いですが、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インターセプトによる、以下を参照])InterlockedExchangeTypedReference


[編集: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です。デフォルトでenumint、基になるタイプが指定されていない場合に使用されるため、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ませsbytecharushortshort)隣接するバイトへの巻き添え被害のリスクを冒すことなく。次の例では、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アクセス全体にのみグロバブルに適用されます。InterlockedInterlockedVolatile.*Thread.Volatile*

言い換えると、32ビットモードで64ビットの不可分操作を取得するには、保証を維持するために、これらのQWORDの場所へのすべてのInterlockedアクセスが/を介して行われる必要があるため、(たとえば)を直接(つまり、非/ )読み取りは、書き込みに常に/関数を使用するという理由だけで保護されます。VolatileInterlockedVolatileInterlockedVolatile

Interlocked最後に、の関数CLRは.NET JITコンパイラによって特別に認識され、特別に扱われることに注意してください。ここここを参照してくださいこの事実は、私が前述した直感に反することを説明するのに役立つかもしれません。

于 2011-04-08T01:36:08.123 に答える
5

[編集:] 私の答えは彼に似ているので、@AnorZakenに謝罪します私は正直に私の投稿する前にそれを見ませんでした。私のテキストと説明が有用であるか、追加の洞察がある場合に備えて、今のところこれを保持しますが、以前の作業の功績は適切にAnorにあります。


このページには別の解決策がありますが、まったく異なるアプローチに興味を持っている人もいるかもしれません。以下に、32ビットまたは64ビットのblittable型DynamicMethodを実装Interlocked.CompareExchangeするaを示します。これには、カスタム型、組み込みメソッドが忘れたプリミティブ型(、)、さらには独自のインスタンスが含まれます。これらは、dword4バイト、つまり、)またはqword8バイト、、 )のサイズです。たとえば、次のタイプはデフォルト以外のサイズを指定しているため、機能しませんEnumuintulongValueTypeintSystem.Int32longSystem.Int64Enumbyte

enum ByteSizedEnum : byte { Foo }     // no: size is not 4 or 8 bytes

ランタイムで生成されたILのほとんどのDynamicMethod実装と同様に、C#コードは見た目が美しくありませんが、一部の人々にとっては、エレガントなILと洗練されたJITtedネイティブコードがそれを補います。たとえば、私が投稿した他の方法とは対照的に、これはC#コードを使用していません。unsafe

呼び出しサイトでジェネリック型の自動推論を可能にするために、ヘルパーをstaticクラスでラップします。

public static class IL<T> where T : struct
{
    // generic 'U' enables alternate casting for 'Interlocked' methods below
    public delegate U _cmp_xchg<U>(ref U loc, U _new, U _old);

    // we're mostly interested in the 'T' cast of it
    public static readonly _cmp_xchg<T> CmpXchg;

    static IL()
    {
        // size to be atomically swapped; must be 4 or 8.
        int c = Marshal.SizeOf(typeof(T).IsEnum ?
                                Enum.GetUnderlyingType(typeof(T)) :
                                typeof(T));

        if (c != 4 && c != 8)
            throw new InvalidOperationException("Must be 32 or 64 bits");

        var dm = new DynamicMethod(
            "__IL_CmpXchg<" + typeof(T).FullName + ">",
            typeof(T),
            new[] { typeof(T).MakeByRefType(), typeof(T), typeof(T) },
            MethodInfo.GetCurrentMethod().Module,
            false);

        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);    // ref T loc
        il.Emit(OpCodes.Ldarg_1);    // T _new
        il.Emit(OpCodes.Ldarg_2);    // T _old
        il.Emit(OpCodes.Call, c == 4 ?
                ((_cmp_xchg<int>)Interlocked.CompareExchange).Method :
                ((_cmp_xchg<long>)Interlocked.CompareExchange).Method);
        il.Emit(OpCodes.Ret);

        CmpXchg = (_cmp_xchg<T>)dm.CreateDelegate(typeof(_cmp_xchg<T>));
    }
};

技術的には、上記が必要なすべてです。これで、適切な値型を呼び出すことができCmpXchgIL<T>.CmpXchg(...)(上記のイントロで説明したように)、組み込みのとまったく同じように動作Interlocked.CompareExchange(...)System.Threadingます。たとえば、struct2つの整数を含むとします。

struct XY
{
    public XY(int x, int y) => (this.x, this.y) = (x, y);   // C#7 tuple syntax
    int x, y;
    static bool eq(XY a, XY b) => a.x == b.x && a.y == b.y;
    public static bool operator ==(XY a, XY b) => eq(a, b);
    public static bool operator !=(XY a, XY b) => !eq(a, b);
}

これで、 CmpXchg操作で期待するのと同じように、64ビット構造体をアトミックに公開できます。これにより、2つの整数がアトミックに公開されるため、別のスレッドが「引き裂かれた」または一貫性のないペアリングを確認することはできません。言うまでもなく、論理ペアを使用して簡単に行うことは、並行プログラミングで非常に役立ちます。さらに、多くのフィールドを使用可能な64(または32)ビットにパックする複雑な構造体を考案する場合はさらに便利です。これを行うための呼び出しサイトの例を次に示します。

var xy = new XY(3, 4);      // initial value

//...

var _new = new XY(7, 8);    // value to set
var _exp = new XY(3, 4);    // expected value

if (IL<XY>.CmpXchg(ref xy, _new, _exp) != _exp)  // atomically swap the 64-bit ValueType
    throw new Exception("change not accepted");

上記で、型推論を有効にすることで呼び出しサイトを整理できるため、ジェネリックパラメーターを指定する必要がないことを説明しました。これを行うには、ジェネリックグローバルクラスの1つで静的ジェネリックメソッドを定義するだけです。

public static class my_globals
{
    [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static T CmpXchg<T>(ref T loc, T _new, T _old) where T : struct => 
                                                 _IL<T>.CmpXchg(ref loc, _new, _old);
}

今回はEnum:を使用して、別の例で簡略化された呼び出しサイトを示します。

using static my_globals;

public enum TestEnum { A, B, C };

static void CompareExchangeEnum()
{
    var e = TestEnum.A;

    if (CmpXchg(ref e, TestEnum.B, TestEnum.A) != TestEnum.A)
        throw new Exception("change not accepted");
}

元の質問については、簡単ulonguint作業することもできます。

ulong ul = 888UL;

if (CmpXchg(ref ul, 999UL, 888UL) != 888UL)
    throw new Exception("change not accepted");
于 2018-02-08T22:56:13.257 に答える
3

おそらく;intの代わりに使用してください。uintのオーバーロードがありintます。範囲の追加ビットが必要ですか?もしそうなら、できるだけ遅くキャスト/変換します。

于 2009-07-12T20:12:17.347 に答える
3

それはまだハックですが、unsafeコードを使用する代わりにIL生成でこれを行うことは可能です。利点は、コンパイラの実装の詳細に依存する代わりに、符号付き型と符号なし型が同じビット長であるという事実に依存していることです。これは仕様の一部です。

方法は次のとおりです。

using System;
using System.Reflection;
using System.Reflection.Emit;
using ST = System.Threading;

/// <summary>
/// Provides interlocked methods for uint and ulong via IL-generation.
/// </summary>
public static class InterlockedUs
{
    /// <summary>
    /// Compares two 32-bit unsigned integers for equality and, if they are equal,
    /// replaces one of the values.
    /// </summary>
    /// <param name="location">
    /// The value to exchange, i.e. the value that is compared with <paramref name="comparand"/> and
    /// possibly replaced with <paramref name="value"/>.</param>
    /// <param name="value">
    /// The value that replaces the <paramref name="location"/> value if the comparison
    /// results in equality.</param>
    /// <param name="comparand">
    /// A value to compare against the value at <paramref name="location"/>.</param>
    /// <returns>The original value in <paramref name="location"/>.</returns>
    public static uint CompareExchange(ref uint location, uint value, uint comparand)
    {
        return ceDelegate32(ref location, value, comparand);
    }

    /// <summary>
    /// Compares two 64-bit unsigned integers for equality and, if they are equal,
    /// replaces one of the values.
    /// </summary>
    /// <param name="location">
    /// The value to exchange, i.e. the value that is compared with <paramref name="comparand"/> and
    /// possibly replaced with <paramref name="value"/>.</param>
    /// <param name="value">
    /// The value that replaces the <paramref name="location"/> value if the comparison
    /// results in equality.</param>
    /// <param name="comparand">
    /// A value to compare against the value at <paramref name="location"/>.</param>
    /// <returns>The original value in <paramref name="location"/>.</returns>
    public static ulong CompareExchange(ref ulong location, ulong value, ulong comparand)
    {
        return ceDelegate64(ref location, value, comparand);
    }


    #region ---  private  ---
    /// <summary>
    /// The CompareExchange signature for uint.
    /// </summary>
    private delegate uint Delegate32(ref uint location, uint value, uint comparand);

    /// <summary>
    /// The CompareExchange signature for ulong.
    /// </summary>
    private delegate ulong Delegate64(ref ulong location, ulong value, ulong comparand);

    /// <summary>
    /// IL-generated CompareExchange method for uint.
    /// </summary>
    private static readonly Delegate32 ceDelegate32 = GenerateCEMethod32();

    /// <summary>
    /// IL-generated CompareExchange method for ulong.
    /// </summary>
    private static readonly Delegate64 ceDelegate64 = GenerateCEMethod64();

    private static Delegate32 GenerateCEMethod32()
    {
        const string name = "CompareExchange";
        Type signedType = typeof(int), unsignedType = typeof(uint);
        var dm = new DynamicMethod(name, unsignedType, new[] { unsignedType.MakeByRefType(), unsignedType, unsignedType });
        var ilGen = dm.GetILGenerator();
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Ldarg_1);
        ilGen.Emit(OpCodes.Ldarg_2);
        ilGen.Emit(
            OpCodes.Call,
            typeof(ST.Interlocked).GetMethod(name, BindingFlags.Public | BindingFlags.Static,
                null, new[] { signedType.MakeByRefType(), signedType, signedType }, null));
        ilGen.Emit(OpCodes.Ret);
        return (Delegate32)dm.CreateDelegate(typeof(Delegate32));
    }

    private static Delegate64 GenerateCEMethod64()
    {
        const string name = "CompareExchange";
        Type signedType = typeof(long), unsignedType = typeof(ulong);
        var dm = new DynamicMethod(name, unsignedType, new[] { unsignedType.MakeByRefType(), unsignedType, unsignedType });
        var ilGen = dm.GetILGenerator();
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Ldarg_1);
        ilGen.Emit(OpCodes.Ldarg_2);
        ilGen.Emit(
            OpCodes.Call,
            typeof(ST.Interlocked).GetMethod(name, BindingFlags.Public | BindingFlags.Static,
                null, new[] { signedType.MakeByRefType(), signedType, signedType }, null));
        ilGen.Emit(OpCodes.Ret);
        return (Delegate64)dm.CreateDelegate(typeof(Delegate64));
    }
    #endregion
}

IL生成のアイデアの「hvd」と列挙型のCompareExchangeメソッドの同様のコードのクレジット。ここにあります。

最初の呼び出しでメソッドを生成するためにいくらかのオーバーヘッドがありますが、生成されたメソッドはデリゲート形式で保存されるため、後続の呼び出しは非常にパフォーマンスが高いはずです。

そして、上記のリンクから引用するには:

生成されたILは、少なくともPEVerifyによれば検証可能であり、これを使用AssemblyBuilderして結果をファイルに保存することで確認できます。

于 2015-06-04T08:10:23.897 に答える
-3

キャストされた式を参照で渡すことはできません。一時変数を使用する必要があります。

public static float Time;
float value2 = (float)SomeValue;
Interlocked.Exchange(ref Time, ref value2);
SomeValue = value2;
于 2009-07-12T21:59:56.037 に答える