2009-07-12 8 views
6

मैं जो इस तरह दिखता है मेरी खेल का एक सा है:सी # Interlocked एक्सचेंज

public static float Time; 

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

मैं समय बदलने के लिए एक uint32 होना चाहते हैं; हालांकि, जब मैं मानों के लिए float के बजाय UInt32 का उपयोग करने का प्रयास करता हूं, तो यह विरोध करता है कि प्रकार एक संदर्भ प्रकार होना चाहिए। Float एक संदर्भ प्रकार नहीं है, इसलिए मुझे पता है कि यह गैर-संदर्भ प्रकारों के साथ ऐसा करने के लिए तकनीकी रूप से संभव है। क्या इस काम को UInt32 के साथ बनाने का कोई व्यावहारिक तरीका है?

उत्तर

12

हालांकि बदसूरत, यह प्रदर्शन करने के लिए वास्तव में संभव है एक परमाणु एक्सचेंज या CompareExchange एक enum पर 64 बिट्स या उससे कम unsafe सी # कोड का उपयोग कर के या अन्य blittable मूल्य के प्रकार:

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

counterintuitive हिस्सा यह है कि रेफरी dereferenced सूचक पर अभिव्यक्ति वास्तव में enum के पते पर प्रवेश करती है। मुझे लगता है कि संकलक इसके अधिकारों के भीतर होता था ताकि इसके बदले स्टैक पर एक अदृश्य अस्थायी चर उत्पन्न हो सके, इस मामले में यह काम नहीं करेगा। अपने जोखिम पार इस्तेमाल करें।

[संपादित करें: ओपी द्वारा अनुरोध विशिष्ट प्रकार के लिए]

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

(मैं भी गैर-दस्तावेजी सी # कीवर्ड __makeref उपयोग करने की कोशिश इसे प्राप्त करने के लिए, लेकिन यह काम नहीं करता है क्योंकि आप एक संदर्भित __refvalue पर ref का उपयोग नहीं कर सकते हैं। यह बहुत खराब है, क्योंकि सीएलआर InterlockedExchange कार्यों को एक निजी आंतरिक फ़ंक्शन पर संचालित करता है जो संचालित करता है TypedReference पर रों [JIT अवरोधन द्वारा रखा टिप्पणी, नीचे देखें])


[संपादित करें: अप्रैल 2017] मैं हाल ही में पता चला कि जब .NETWOW में यानी 32-बिट मोड में चल रहा है (या, उपप्रणाली), 64-बिट Interlocked संचालन गैर-Interlocked, उसी स्मृति स्थानों के "बाहरी" दृश्यों के संबंध में परमाणु होने की गारंटी है। 32-बिट मोड में, परमाणु गारंटी केवल QWORD पहुंच में ग्लोबब्लली पर लागू होती है जो Interlocked (और शायद Volatile.*, या Thread.Volatile*, TBD?) फ़ंक्शंस का उपयोग करती है।

दूसरे शब्दों में, 32-बिट मोड में 64-बिट परमाणु संचालन प्राप्त करने के लिए, सभी QWORD करने के लिए स्थानों आदेश की गारंटी देता है की रक्षा करने के में Interlocked के माध्यम से होने चाहिए पहुँचता है, और आप यह सोचते हैं कि प्यारा नहीं मिल सकता है (उदाहरण के लिए) प्रत्यक्ष पठन केवल इसलिए संरक्षित हैं क्योंकि आप हमेशा लिखने के लिए Interlocked फ़ंक्शंस का उपयोग करते हैं।

अंत में, ध्यान दें कि में Interlocked फ़ंक्शन विशेष रूप से मान्यता प्राप्त हैं, और .NET JIT कंपाइलर में विशेष उपचार प्राप्त करते हैं। here और here देखें यह तथ्य पहले उल्लेखित काउंटर-अंतर्ज्ञान की व्याख्या करने में मदद कर सकता है।

+0

मैं वास्तव में यह स्वीकार्य उत्तर देने जा रहा हूं क्योंकि मैं इस तरह UIU3232 के लिए तुलना एक्सचेंज लिख सकता हूं, जो मूल प्रश्न था। – Martin

+0

यह काम जानना अच्छा लगता है, लेकिन मुझे लगता है कि आपके "अपने जोखिम पर उपयोग" चेतावनी बहुत दृढ़ता से जोर दिया जाना चाहिए। यदि कंपाइलर के अगले संस्करण ने यहां एक अस्थायी उत्पन्न किया है, तो यह चुपचाप एक बग पेश करेगा जो ट्रैक करने में बहुत मुश्किल हो सकता है। मैं एक बड़ा यूनिट-परीक्षक नहीं हूं, लेकिन इस तरह की चाल मूल रूप से एक की मांग करती है। फिर भी, के बारे में जानने के लिए एक अच्छी बात है। ऊपर मतदान किया। – Gabriel

+0

मैंने इसका परीक्षण नहीं किया है, लेकिन मुझे संदेह है कि डायनामिक मोड और आईएल-पीढ़ी का उपयोग करके 'असुरक्षित' के बिना यह संभव है। (चूंकि यह enums के लिए संभव है, [जैसा कि यहां दिखाया गया है] (http://stackoverflow.com/a/18359360/533837)।) यह अभी भी एक "हैक" है, लेकिन मुझे नहीं लगता कि यह कंपाइलर कार्यान्वयन पर निर्भर करेगा । – AnorZaken

2

शायद uint के बजाय int का उपयोग करें; int के लिए ओवरलोड हैं। क्या आपको अतिरिक्त सीमा की आवश्यकता है? यदि ऐसा है, तो जितना संभव हो सके कलाकार/कास्ट करें।

+0

वैसे मैं शून्य से कम मानों का कभी भी उपयोग नहीं करता (क्योंकि यह गेम शुरू होने के बाद से समय संग्रहित कर रहा है), हालांकि (2^32)/2 अभी भी एक बहुत लंबा गेम है (यह मिलीसेकंड संग्रहीत कर रहा है। मुझे लगता है कि अब मैं इनट्स का उपयोग करूंगा और इसे छोड़ दूंगा। – Martin

16

(double, int, long, IntPtr और object के लिए और अन्य) float के लिए विशेष रूप Interlocked.Exchange के लिए एक अधिभार नहीं है। यूंट के लिए कोई नहीं है, इसलिए संकलक निकटतम मैच मानता है जेनेरिक Interlocked.Exchange<T> - लेकिन उस स्थिति में T एक संदर्भ प्रकार होना चाहिए। uint एक संदर्भ प्रकार नहीं है, इसलिए यह काम नहीं करता है - इसलिए त्रुटि संदेश।

दूसरे शब्दों में:

  • आपके मौजूदा कोड काम करता है क्योंकि यह Interlocked.Exchange(ref float, float) कहता है।
  • इसे uint पर बदलना विफल रहता है क्योंकि कोई लागू अधिभार नहीं है। सटीक त्रुटि संदेश संकलक अनुमान के कारण होता है जिसका अर्थ है कि आप Interlocked.Exchange<T>(ref T, T) हैं। ,

    • संभावित बजाय int का उपयोग के रूप में मार्क पता चलता है:

    क्या करना है का सवाल है, विकल्पों में से किसी रहे हैं।

  • यदि आपको अतिरिक्त सीमा की आवश्यकता है, तो long का उपयोग करने के बारे में सोचें।
  • उपयोग uint लेकिन हालांकि स्पष्ट रूप से Exchangeके साथ ठीक कुछ विशिष्ट मान प्रकार काम करता है ताला मुक्त कोड

लिखने की कोशिश नहीं करते हैं, माइक्रोसॉफ्ट सभी आदिम प्रकार के लिए इसे लागू नहीं किया है। मैं कल्पना नहीं कर सकता कि ऐसा करना मुश्किल होगा (वे बस बिट्स हैं, आखिरकार) लेकिन संभवतः वे ओवरलोड गिनती को रखना चाहते थे।

+0

'इंटरलाक्ड' को बीसीएल में प्रत्येक गैर-तुच्छनीय 64-बिट (या छोटे) मान प्रकार के लिए अधिभार प्रदान करना चाहिए और कम कुछ भी करने का कोई बहाना नहीं है। विडंबना यह है कि देशी सी/सी ++ के विपरीत (जहां कंपाइलर-ऑप्टिमाइज़ेशन बुत ** लॉक-फ्री ** डिज़ाइन के सामान्य नुकसान के लिए मेमोरी एक्सेस गारंटी को कमजोर करता है), .NET में लोहा-पहना हुआ मेमोरी मॉडल वास्तव में सक्षम बनाता है-या यहां तक ​​कि प्रोत्साहित करता है-व्यापक उपयोग गैर-तुच्छ तरीकों में लॉक-फ्री कोड का। परमाणु परिचालनों के इस बढ़ते महत्व को देखते हुए, यदि [मेरा कामकाज] (/ ए/558 9 515/147511) उपलब्ध नहीं था, तो निरीक्षण कमजोर हो जाएगा। –

-3

आप संदर्भ द्वारा एक casted अभिव्यक्ति पारित नहीं हो सकता, आप एक अस्थायी चर का उपयोग करना चाहिए:

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

ओह, कास्ट सिर्फ यह दिखाने के लिए था कि वह किस प्रकार का मूल्य है, मुझे नहीं पता था कि आप वहां कास्ट नहीं कर सकते: ओ – Martin

+0

दूसरे चर को संदर्भ होने की आवश्यकता नहीं है, यहां एमएसडीएन देखें: http : //msdn.microsoft.com/en-us/library/5z8f2s39.aspx – Martin

+5

चर की एक प्रति बनाना Interlocked.Exchange के उद्देश्य को हरा देता है। –

2

यह अभी भी हैक है लेकिन unsafe कोड का उपयोग करने के बजाय आईएल-पीढ़ी के साथ ऐसा करना संभव है। लाभ यह है कि एक कंपाइलर कार्यान्वयन विस्तार पर निर्भर होने के बजाय यह इस तथ्य पर निर्भर करता है कि हस्ताक्षरित और हस्ताक्षरित प्रकार एक ही बिट-लंबाई के हैं, जो कि spec का हिस्सा है।

यहाँ है कैसे:

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 
} 

क्रेडिट आईएल पीढ़ी विचार और Enums के लिए एक CompareExchange विधि, here पाया जा सकता है के लिए इसी तरह के कोड के लिए "HVD" करने के लिए।

पहले कॉल पर विधि उत्पन्न करने के लिए कुछ ओवरहेड होगा, लेकिन जेनरेट की गई विधि प्रतिनिधि रूप में संग्रहीत की जाती है ताकि किसी भी बाद की कॉल बहुत ही सक्षम हो।

और ऊपर के लिंक से उद्धृत करने के लिए:

उत्पन्न आईएल के रूप में इस प्रयोग AssemblyBuilder बनाने और एक फाइल करने के लिए परिणाम बचत द्वारा जाँच की जा सकती है, कम से कम PEVerify के अनुसार निरीक्षण करता है।

1

[संपादित करें:]विदेश मंत्रालय culpa और क्षमा याचना @AnorZaken के बाद से मेरा उत्तर उसकी के समान है। मैं ईमानदारी से मेरा पोस्ट करने से पहले इसे नहीं देखा था। यदि मैं अपने टेक्स्ट और स्पष्टीकरण उपयोगी हैं या अतिरिक्त अंतर्दृष्टि हैं, तो मैं इसे अभी रखूंगा, लेकिन पूर्व कार्य के लिए क्रेडिट ठीक से अनोर को जाता है।


हालांकि मैं इस पृष्ठ पर another solution है, कुछ लोगों को एक पूरी तरह से अलग दृष्टिकोण में रुचि हो सकती। नीचे, मैंने DynamicMethod जो किसी भी 32- या 64-बिट blittable प्रकार है, जो किसी भी कस्टम Enum प्रकार, आदिम प्रकार है कि निर्मित विधि भूल गया (uint, ulong) भी शामिल है के लिए Interlocked.CompareExchange लागू करता है, और यहां तक ​​कि अपने खुद ValueType उदाहरणों - जब तक कि इनमें से किसी भी DWORD (4-बाइट, यानी, int, System.Int32) या QWORD (8 बाइट्स, long, System.Int64) आकार के होते हैं।

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

क्रम-उत्पन्न आईएल के सबसे DynamicMethod कार्यान्वयन के साथ के रूप में, सी # कोड प्रतिसाद नहीं: उदाहरण के लिए, Enum प्रकार नहीं होगा काम के बाद से यह एक गैर डिफ़ॉल्ट आकार, byte निर्दिष्ट करता है निम्नलिखित देखने के लिए सुंदर नहीं है, लेकिन कुछ लोगों के लिए सुरुचिपूर्ण आईएल और चिकना जिटेटेड देशी कोड इसके लिए तैयार है। उदाहरण के लिए, मैंने पोस्ट की गई अन्य विधि के विपरीत, यह 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(...) पर किसी उचित मूल्य प्रकार (जैसा कि ऊपर दिए गए परिचय में चर्चा की गई है) पर कॉल कर सकते हैं, और यह System.Threading में अंतर्निहित Interlocked.CompareExchange(...) जैसा व्यवहार करेगा।

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

अब आप atomically 64-बिट struct बस के रूप में आप किसी भी CmpXchg संचालन के साथ उम्मीद करेंगे प्रकाशित कर सकते हैं: उदाहरण के लिए, यदि आप एक struct युक्त दो पूर्णांकों है कहते हैं की सुविधा देता है। यह परमाणु रूप से दो पूर्णांक प्रकाशित करता है ताकि किसी अन्य धागे के लिए 'टूटा' या असंगत जोड़ी देखना असंभव हो। कहने की जरूरत नहीं है, लॉजिकल जोड़ी के साथ आसानी से ऐसा करना समवर्ती प्रोग्रामिंग में बेहद उपयोगी है, और यदि आप एक विस्तृत संरचना तैयार करते हैं जो उपलब्ध 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"); 

ऊपर, मैंने कहा कि आप ऐसा है कि आप जेनेरिक पैरामीटर निर्दिष्ट करने की जरूरत नहीं प्रकार निष्कर्ष को सक्षम करने से कॉल साइट को साफ़ रखने कर सकते हैं। ऐसा करने के लिए, बस अपने गैर सामान्य वैश्विक वर्गों में से एक में एक स्थिर सामान्य विधि को परिभाषित:

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

मूल प्रश्न का सवाल है, ulong और uint काम तुच्छता के साथ-साथ:

ulong ul = 888UL; 

if (CmpXchg(ref ul, 999UL, 888UL) != 888UL) 
    throw new Exception("change not accepted"); 
संबंधित मुद्दे