2009-07-12 8 views
6

Tôi có một chút của trò chơi của tôi trông như thế này:C# đan cài Trao đổi

public static float Time; 

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

Tôi muốn thay đổi Thời gian là một uint32; tuy nhiên, khi tôi cố gắng sử dụng UInt32 thay vì float cho các giá trị, nó phản đối loại phải là loại tham chiếu. Float không phải là loại tham chiếu, vì vậy tôi biết về mặt kỹ thuật có thể thực hiện việc này với các loại không tham chiếu. Có cách nào thực tế để thực hiện công việc này với UInt32 không?

Trả lời

12

Mặc dù xấu xí, nó thực sự có thể thực hiện một nguyên tử Trao đổi hoặc CompareExchange trên một enum hoặc loại giá trị blittable khác 64 bit hoặc ít hơn bằng cách sử dụng unsafe C# code:

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 
     } 
} 

phần phản trực giác là biểu thức ref trên con trỏ không quan tâm hiện thực sự xuyên qua địa chỉ của enum. Tôi nghĩ rằng trình biên dịch sẽ có được trong quyền của mình để tạo ra một biến tạm thời vô hình trên stack thay vào đó, trong trường hợp này sẽ không hoạt động. Sử dụng có nguy cơ của riêng bạn.

[sửa: cho các loại hình cụ thể theo yêu cầu của 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); 
} 

[sửa: và 64-bit unsigned dài]

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); 
} 

(Tôi cũng đã cố gắng sử dụng không có giấy tờ C# từ khóa __makeref để đạt được điều này, nhưng điều này không hiệu quả vì bạn không thể sử dụng ref trên một số __refvalue dreferenced Nó quá xấu, vì CLR ánh xạ các hàm InterlockedExchange đến một hàm riêng bên trong hoạt động s trên TypedReference [bình luận tranh luận bởi JIT chặn, xem dưới đây])


[sửa: Tháng 4 năm 2017] Gần đây tôi đã học được rằng khi .NET đang chạy trong chế độ 32-bit (hoặc, tức là trong WOW hệ thống con), các hoạt động Interlocked 64 bit là không phải được đảm bảo là nguyên tử đối với các góc nhìn non-Interlocked, "bên ngoài" của cùng một vị trí bộ nhớ. Trong chế độ 32 bit, bảo đảm nguyên tử chỉ áp dụng globablly trên các truy nhập QWORD sử dụng các hàm Interlocked (và có lẽ là Volatile.* hoặc Thread.Volatile*, TBD?).

Nói cách khác, để có được hoạt động nguyên tử 64-bit ở chế độ 32-bit, tất cả truy cập để QWORD địa điểm phải xảy ra thông qua Interlocked để giữ gìn bảo lãnh, và bạn không thể có được dễ thương giả định rằng (ví dụ) đọc trực tiếp được bảo vệ chỉ vì bạn luôn sử dụng các hàm Interlocked để viết.

Cuối cùng, lưu ý rằng các hàm Interlocked trong CLR được nhận dạng đặc biệt bởi và nhận điều trị đặc biệt trong trình biên dịch .NET JIT. Xem herehere Thực tế này có thể giúp giải thích sự phản đối trực quan mà tôi đã đề cập trước đó.

+0

Tôi thực sự sẽ cung cấp cho câu trả lời này được chấp nhận bởi vì tôi có thể easilt viết một CompareExchange cho UInt32 theo cách này, đó là câu hỏi ban đầu. – Martin

+0

Thật tuyệt khi biết điều này hoạt động, nhưng tôi nghĩ rằng cảnh báo "sử dụng rủi ro của riêng bạn" cần được nhấn mạnh rất mạnh. Nếu phiên bản tiếp theo của trình biên dịch tạo ra một temp ở đây, nó sẽ âm thầm giới thiệu một lỗi có thể rất khó để theo dõi. Tôi không phải là một đơn vị thử nghiệm lớn, nhưng loại mẹo này về cơ bản đòi hỏi một. Tuy nhiên, một điều tốt để biết về. Lên tiếng bình chọn. – Gabriel

+0

Tôi chưa thử nghiệm nó, nhưng tôi nghi ngờ điều này là có thể mà không có 'không an toàn' bằng cách sử dụng DynamicMethod & IL-generation. (Vì nó có thể cho enums, [như được hiển thị ở đây] (http://stackoverflow.com/a/18359360/533837).) Nó vẫn là một "hack", nhưng tôi không nghĩ rằng nó sẽ phụ thuộc vào việc thực hiện trình biên dịch . – AnorZaken

2

Có thể sử dụng int thay vì uint; có quá tải cho int. Bạn có cần thêm một chút phạm vi không? Nếu có, hãy truyền/chuyển đổi càng muộn càng tốt.

+0

Tôi chưa bao giờ sử dụng các giá trị nhỏ hơn 0 (vì nó lưu trữ thời gian kể từ khi trò chơi bắt đầu), tuy nhiên (2^32)/2 vẫn là một trò chơi rất dài (nó lưu trữ mili giây). Tôi đoán bây giờ tôi sẽ chỉ sử dụng ints và để nó ở đó. – Martin

16

Có một tình trạng quá tải cho Interlocked.Exchange đặc biệt cho float (và những người khác cho double, int, long, IntPtrobject). Không có một cho uint, do đó, trình biên dịch tính toán kết quả gần nhất là Interlocked.Exchange<T> chung - nhưng trong trường hợp đó, T phải là loại tham chiếu. uint không phải là loại tham chiếu, do đó không hoạt động - do đó là thông báo lỗi.

Nói cách khác:

Đối với những việc cần làm, các tùy chọn bất kỳ của:

  • có khả năng sử dụng int thay vào đó, như Marc gợi ý.
  • Nếu bạn cần thêm dải, hãy nghĩ đến việc sử dụng long.
  • Sử dụng uint nhưng đừng cố gắng viết lock-free đang

Mặc dù rõ ràng Exchange hoạt động tốt với một số kiểu giá trị cụ thể, Microsoft đã không được thực hiện nó cho tất cả các loại nguyên thủy. Tôi không thể tưởng tượng nó sẽ có được khó khăn để làm như vậy (họ chỉ là bit, sau khi tất cả) nhưng có lẽ họ muốn giữ số lượng quá tải xuống.

+0

'Interlocked' sẽ cung cấp quá tải cho mọi loại giá trị nhỏ hơn 64 bit (hoặc nhỏ hơn) trong BCL và không có lý do gì cho bất kỳ thứ gì ít hơn. Trớ trêu thay, không giống như C/C++ bản địa (nơi mà tối ưu hóa trình biên dịch làm suy yếu truy cập bộ nhớ đảm bảo cho tổn hại chung của ** thiết kế ** không khóa), mô hình bộ nhớ bọc sắt trong .NET thực sự cho phép - hoặc thậm chí khuyến khích - sử dụng rộng hơn mã không khóa theo các cách không tầm thường. Với tầm quan trọng cao của các hoạt động nguyên tử, nếu [workaround của tôi] (/ a/5589515/147511) không có sẵn, sự giám sát sẽ hết sức kinh khủng. –

-3

Bạn không thể vượt qua một biểu đúc bằng cách tham khảo, bạn nên sử dụng một biến tạm thời:

public static float Time; 
float value2 = (float)SomeValue; 
Interlocked.Exchange(ref Time, ref value2); 
SomeValue = value2; 
+0

Ồ, dàn diễn viên chỉ để chứng minh loại giá trị đó là gì, tôi không nhận ra bạn không thể sử dụng phôi ở đó: O – Martin

+0

biến thứ hai không cần phải tham khảo, hãy xem MSDN tại đây: http : //msdn.microsoft.com/en-us/library/5z8f2s39.aspx – Martin

+5

Tạo một bản sao của biến sẽ đánh bại mục đích của Interlocked.Exchange. –

2

Nó vẫn là một hack nhưng nó có thể làm điều này với thế hệ IL thay vì sử dụng unsafe mã. Lợi ích là thay vì dựa vào một chi tiết triển khai trình biên dịch, nó dựa trên thực tế là các loại đã ký và chưa ký có cùng độ dài bit, là một phần của đặc tả.

Sau đây là cách:

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 
} 

tín dụng để "HVD" cho ý tưởng IL-thế hệ và mã tương tự cho một phương pháp CompareExchange cho Enums, có thể được tìm thấy here.

Sẽ có một số chi phí để tạo phương thức trên cuộc gọi đầu tiên, nhưng phương thức được tạo sẽ được lưu trữ dưới dạng đại biểu để mọi cuộc gọi tiếp theo phải rất hiệu quả.

Và trích dẫn từ các liên kết ở trên:

Các IL được tạo ra là có thể kiểm chứng, ít nhất là theo PEVerify, như có thể được kiểm tra bằng cách sử dụng này AssemblyBuilder và lưu kết quả vào một tập tin.

1

[sửa:]Mea culpa và lời xin lỗi đến @AnorZaken từ câu trả lời của tôi cũng tương tự như mình. Tôi thành thật không nhìn thấy nó trước khi gửi bài của tôi. Tôi sẽ giữ điều này ngay bây giờ trong trường hợp văn bản và giải thích của tôi hữu ích hoặc có thông tin chi tiết bổ sung, nhưng tín dụng cho công việc trước đó đúng cách sẽ được chuyển đến Anor.


Mặc dù tôi có another solution trên trang này, một số người có thể quan tâm đến một cách tiếp cận hoàn toàn khác. Dưới đây, tôi đưa ra một DynamicMethod mà thực hiện Interlocked.CompareExchange cho bất kỳ 32 hoặc 64-bit blittable loại, trong đó bao gồm bất kỳ tùy chỉnh Enum loại, các kiểu primitive mà được xây dựng trong phương pháp quên (uint, ulong), và thậm chí bạn riêng ValueType trường - chừng nào bất kỳ trong số này là dword (4-byte, tức là, int, System.Int32) hoặc qword (8-byte, long, System.Int64) kích thước. Ví dụ, sau đây Enum loại sẽ không làm việc kể từ khi nó chỉ định kích thước không phải mặc định, byte:

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

Như với hầu hết DynamicMethod triển khai của runtime tạo IL, các C# đang isn Thật tuyệt vời khi nhìn thấy, nhưng đối với một số người, mã nguồn gốc JITted IL và kiểu dáng đẹp trang nhã tạo nên điều đó. Ví dụ, trái ngược với phương pháp khác tôi đăng, phương pháp này không sử dụng mã số unsafe C#.

Để cho phép tự động suy luận của các loại chung tại địa điểm cuộc gọi, tôi quấn helper trong một lớp học 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>)); 
    } 
}; 

Về mặt kỹ thuật, phía trên là tất cả các bạn cần. Bây giờ bạn có thể gọi CmpXchgIL<T>.CmpXchg(...) trên bất kỳ loại giá trị thích hợp nào (như được thảo luận trong phần giới thiệu ở trên) và nó sẽ hoạt động chính xác như được xây dựng trong Interlocked.CompareExchange(...) trong System.Threading.Ví dụ, cho phép nói rằng bạn có một struct chứa hai số nguyên:

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); 
} 

Bạn bây giờ có thể nguyên tử xuất bản 64-bit struct cũng giống như bạn mong chờ với bất kỳ hoạt độngCmpXchg. Điều này xuất bản nguyên tử hai số nguyên sao cho không thể cho một chuỗi khác nhìn thấy một cặp 'bị rách' hoặc không nhất quán. Không cần phải nói, dễ dàng làm như vậy với một cặp logic là cực kỳ hữu ích trong lập trình đồng thời, thậm chí nhiều hơn như vậy nếu bạn đưa ra một cấu trúc phức tạp mà gói nhiều trường vào 64 bit (hoặc 32) có sẵn. Dưới đây là ví dụ về trang web gọi để thực hiện việc này:

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"); 

Ở trên, tôi đã đề cập rằng bạn có thể dọn dẹp trang web cuộc gọi để không phải chỉ định tham số chung. Để làm điều này, chỉ cần xác định một tĩnh generic phương pháp ở một trong lớp toàn cầu chung phi bạn:

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); 
} 

tôi sẽ hiển thị các trang web gọi đơn giản với một ví dụ khác, lần này sử dụng một 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"); 
} 

đối với câu hỏi ban đầu, ulonguint việc trivially cũng như:

ulong ul = 888UL; 

if (CmpXchg(ref ul, 999UL, 888UL) != 888UL) 
    throw new Exception("change not accepted");