2012-12-28 33 views
11

कहो मैं इस समारोह के साथ एक कोड ब्लॉक में प्रवेश (मान मैं एक threadsafe तरह से कैश तक पहुँचने हूँ):दो धागे रोकें एक ही मूल्य

object GetCachedValue(string id) 
{ 
    if (!Cache.ContainsKey(id)) 
    { 
     //long running operation to fetch the value for id 
     object value = GetTheValueForId(id); 
     Cache.Add(id, value); 
    }  
    return Cache[id]; 
} 

मैं "चलने से दो धागे को रोकने के लिए चाहते हैं लंबे समय तक चलने वाला ऑपरेशन "उसी समय उसी मान के लिए। जाहिर है, मैं पूरी चीज को लॉक() में लपेट सकता हूं, लेकिन फिर पूरा कार्य मूल्य के बावजूद ब्लॉक करेगा और मैं चाहता हूं कि दो धागे लंबे समय तक चलने वाले ऑपरेशन को करने में सक्षम हों, जब तक कि वे अलग-अलग आईडी की तलाश में हों।

वहाँ जबकि अन्य धागा लंबी चलने आपरेशन तो मैं दो बार (या एन बार) यह करने के लिए की जरूरत नहीं है पूरा करता है एक अंतर्निहित तंत्र ताला लगा एक मूल्य के आधार पर लॉक करने तो एक धागा ब्लॉक कर सकते हैं है? आदर्श रूप से जब तक लंबे समय तक चलने वाला ऑपरेशन एक थ्रेड में किया जा रहा है, तो कोई अन्य थ्रेड उसी आईडी मान के लिए ऐसा करने में सक्षम नहीं होना चाहिए।

मैं आईडी को हैशसेट में डालकर और फिर ऑपरेशन पूरा होने के बाद उन्हें हटाकर अपना खुद का रोल कर सकता हूं, लेकिन यह एक हैक जैसा लगता है।

+0

आप String.Intern (आईडी) पर एक ताला कर सकता है। लेकिन फिर आपको यह सुनिश्चित करना चाहिए कि आपका "आईडी" स्ट्रिंग आपके ऐप में बहुत (!) अद्वितीय है;) – igrimpe

उत्तर

7

मैं Lazy<T> यहाँ का प्रयोग करेंगे। कोड के नीचे कैश लॉक करेगा, Lazy को कैश में डालें और तुरंत लौटें। लंबे समय से चलने वाले ऑपरेशन को थ्रेड सुरक्षित तरीके से एक बार निष्पादित किया जाएगा।

new Thread(() => Console.WriteLine("1-" + GetCachedValue("1").Value)).Start(); 
new Thread(() => Console.WriteLine("2-" + GetCachedValue("1").Value)).Start(); 

Lazy<object> GetCachedValue(string id) 
{ 
    lock (Cache) 
    { 
     if (!Cache.ContainsKey(id)) 
     { 
      Lazy<object> lazy = new Lazy<object>(() => 
       { 
        Console.WriteLine("**Long Running Job**"); 
        Thread.Sleep(3000); 
        return int.Parse(id); 
       }, 
       true); 

      Cache.Add(id, lazy); 
      Console.WriteLine("added to cache"); 
     } 
     return Cache[id]; 
    } 
} 
+0

रेस हालत: कैश से पढ़ने के साथ समवर्ती रूप से पढ़ना। – usr

+0

@Usr मैं किसी भी प्रदर्शन को चोट पहुंचाए बिना 'लॉक कैश [आईडी];' आसानी से 'लॉक' ब्लॉक में स्थानांतरित कर सकता हूं, लेकिन कोई दौड़ स्थिति नहीं है। 'वापसी कैश [आईडी];' 'Lazy ' –

+0

वापस करने की गारंटी है, निश्चित रूप से, लोग चाहते थे कि लोग सचमुच कोड कॉपी न करें और अजीब रनटाइम त्रुटियां न करें। – usr

0

अपनी लॉकिंग को अपनी टिप्पणी कहां ले जाएं। मुझे लगता है कि आपको वर्तमान में निष्पादित लंबे चल रहे संचालन की सूची बनाए रखने की आवश्यकता है, और उस सूची तक पहुंच को लॉक करें, और केवल GetValueForId निष्पादित करें यदि id आप उस सूची में नहीं हैं। मैं कुछ कोशिश करूँगा और चाबुक करूंगा।

private List<string> m_runningCacheIds = new List<string>(); 

object GetCachedValue(string id) 
{ 
    if (!Cache.ContainsKey(id)) 
    { 
     lock (m_runningCacheIds) { 
      if (m_runningCacheIds.Contains(id)) { 
       // Do something to wait until the other Get is done.... 
      } 
      else { 
       m_runningCacheIds.Add(id); 
      } 
     } 

     //long running operation to fetch the value for id 

     object value = GetTheValueForId(id); 
     Cache.Add(id, value); 

     lock (m_runningCacheIds) 
      m_runningCacheIds.Remove(id); 
    }  
    return Cache[id]; 
} 

अभी भी यह मुद्दा है कि थ्रेड क्या करने जा रहा है, जबकि यह अन्य धागे पर इंतजार कर रहा है।

+1

इससे डेडलॉक होगा। पहले लाने के बाद, यह m_runningCacheIds को लॉक करने का प्रयास करेगा, जो वर्तमान में दूसरे द्वारा लॉक किया गया है क्योंकि यह पहले खत्म होने की प्रतीक्षा करता है। – CodeNaked

+0

@CodeNaked - ठीक है, मैं थ्रेड के लिए कोड में उस बिंदु पर वास्तव में प्रतीक्षा करने का अर्थ नहीं था। मुझे अभी तक पता नहीं लगाया गया कि कैश परिणाम पर इसे कैसे इंतजार करना है। –

-2

यह दुनिया का सबसे सुरुचिपूर्ण समाधान नहीं है, लेकिन मैं इस समस्या के समाधान के लिए एक दोहरी जांच और एक ताला के साथ मिल गया है:

object GetCachedValue(string id) 
{ 
    if (!Cache.ContainsKey(id)) 
    { 
     lock (_staticObj) 
     { 
      if (!Cache.ContainsKey(id)) 
      { 
       //long running operation to fetch the value for id 
       object value = GetTheValueForId(id); 
       Cache.Add(id, value); 
      } 
     } 
    }  
    return Cache[id]; 
} 
+0

समस्या यह है कि यह * सभी * पहचानकर्ताओं के लिए 'GetTheValueForId' लॉक करता है। मुझे लगता है कि ओपी विभिन्न पहचानकर्ताओं के लिए कई समवर्ती निष्पादन संभव चाहता है। –

+0

-1: यह लंबे समय से चलने वाला ऑपरेशन पूरा होने में मदद करता है और नया मान संग्रहीत होता है, लेकिन तब तक ताले लगाता है। –

0

मैं में उपयोग करने वाले मामलों Mutex के रूप में:

object GetCachedValue(string Key) 
{ 
    // note here that I use the key as the name of the mutex 
    // also here you need to check that the key have no invalid charater 
    // to used as mutex name. 
    var mut = new Mutex(true, key); 

    try 
    { 
     // Wait until it is safe to enter. 
     mut.WaitOne(); 

     // here you create your cache 
     if (!Cache.ContainsKey(Key)) 
     { 
      //long running operation to fetch the value for id 
      object value = GetTheValueForId(Key); 
      Cache.Add(Key, value); 
     }  

     return Cache[Key];   
    } 
    finally 
    { 
     // Release the Mutex. 
     mut.ReleaseMutex(); 
    } 
} 

नोट्स:

  • कुछ वर्ण स्लैश की तरह (म्युटेक्स नाम के लिए मान्य नहीं हैं)
  • यदि आपके द्वारा उपयोग किए जाने वाले प्रत्येक एप्लिकेशन (या वेब पूल) के लिए कैश अलग है, और यह है कि हम एएसपीनेट के कैश के लिए बोलते हैं, तो म्यूटेक्स कंप्यूटर में सभी धागे और पूल लॉक कर देता है, इस मामले में मैं भी एक स्थिर यादृच्छिक पूर्णांक का उपयोग करें जिसे मैं कुंजी में जोड़ता हूं, और प्रत्येक कुंजी के लिए लॉक को अलग नहीं करता बल्कि प्रत्येक पूल के लिए भी अलग करता हूं।
0

इस मामले में मैं इस

using (SyncDispatcher.Enter(id)) 
{ 
    //any code here... 
} 

तरह इंटरफेस के लिए तो मैं किसी भी कोड निष्पादित कर सकता है चाहते हैं और यह सुरक्षित थ्रेड किया जाएगा अगर आईडी एक ही है। यदि मुझे कैश से मूल्य प्राप्त करने की आवश्यकता है तो मुझे इसे सीधे आगे बढ़ाना चाहिए, क्योंकि इसमें कोई सहमति नहीं है।

SyncDispatcher के लिए मेरे कार्यान्वयन यह है:

public class SyncDispatcher : IDisposable 
{ 
    private static object _lock = new object(); 
    private static Dictionary<object, SyncDispatcher> _container = new Dictionary<object, SyncDispatcher>(); 

    private AutoResetEvent _syncEvent = new AutoResetEvent(true); 

    private SyncDispatcher() { } 

    private void Lock() 
    { 
     _syncEvent.WaitOne(); 
    } 

    public void Dispose() 
    { 
     _syncEvent.Set(); 
    } 

    public static SyncDispatcher Enter(object obj) 
    { 
     var objDispatcher = GetSyncDispatcher(obj); 
     objDispatcher.Lock(); 

     return objDispatcher; 
    } 

    private static SyncDispatcher GetSyncDispatcher(object obj) 
    { 
     lock (_lock) 
     { 
      if (!_container.ContainsKey(obj)) 
      { 
       _container.Add(obj, new SyncDispatcher()); 
      } 

      return _container[obj]; 
     } 
    } 
} 

सरल टेस्ट:

static void Main(string[] args) 
{ 
    new Thread(() => Execute("1", 1000, "Resource 1")).Start(); 
    new Thread(() => Execute("2", 200, "Resource 2")).Start(); 
    new Thread(() => Execute("1", 0, "Resource 1 again")).Start(); 
} 

static void Execute(object id, int timeout, string message) 
{ 
    using (SyncDispatcher.Enter(id)) 
    { 
     Thread.Sleep(timeout); 

     Console.WriteLine(message);    
    } 
} 

enter image description here