2010-02-25 9 views
11

मुझे लगता है कि मुझे इसका जवाब पता होना चाहिए, लेकिन अगर मैं संभावित रूप से आपदाजनक गलती कर रहा हूं तो मैं वैसे भी पूछने जा रहा हूं।क्या यह सिग्नल करने के लिए सुरक्षित है और मैन्युअल रीसेट Event बंद करें?

निम्नलिखित कोड निष्पादित करता है कोई त्रुटि/अपवादों के साथ के रूप में उम्मीद:

static void Main(string[] args) 
{ 
    ManualResetEvent flag = new ManualResetEvent(false); 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     flag.WaitOne(); 
     Console.WriteLine("Work Item 1 Executed"); 
    }); 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     flag.WaitOne(); 
     Console.WriteLine("Work Item 2 Executed"); 
    }); 
    Thread.Sleep(1000); 
    flag.Set(); 
    flag.Close(); 
    Console.WriteLine("Finished"); 
} 
बेशक

, के रूप में आम तौर पर मल्टी-थ्रेडेड कोड के साथ मामला है, एक सफल परीक्षण साबित नहीं होता कि यह वास्तव में सुरक्षित थ्रेड है। अगर मैं CloseSet से पहले Close डालता हूं, तो परीक्षण भी सफल होता है, भले ही दस्तावेज़ स्पष्ट रूप से बताता है कि Close के बाद कुछ भी करने का प्रयास करने से अपरिभाषित व्यवहार होगा।

मेरा प्रश्न है, जब मैं ManualResetEvent.Set विधि आह्वान, यह सभी इंतजार कर धागे फोन करने वाले के लिए नियंत्रण लौटने से पहले संकेत करने के लिए गारंटी है? दूसरे शब्दों में, यह मानते हुए कि मैं गारंटी दे सकता हूं कि WaitOne पर कोई और कॉल नहीं होगी, क्या यह यहां हैंडल को बंद करना सुरक्षित है, या यह संभव है कि कुछ परिस्थितियों में यह कोड कुछ वेटर्स को संकेत या परिणाम प्राप्त करने से रोक देगा ObjectDisposedException में?

प्रलेखन केवल कहते हैं Set में एक "राज्य का संकेत" कहते हैं कि - इसके बारे में जब वेटर वास्तव में कि संकेत मिल जाएगा कोई दावा नहीं लगता है, तो मैं सुनिश्चित हो करना चाहते हैं।

+0

स्लीप को वेटऑन() से पहले होने के लिए एक दिलचस्प प्रयोग होगा। इससे आपको यह जांचने की अनुमति मिल जाएगी कि 'ध्वज' बंद होने पर WaitOne() क्या करता है() 'डी। –

+0

@ फिलिप नगन: यह वास्तव में अपरिभाषित है, जैसा कि दस्तावेज़ीकरण से पता चलता है। यदि आप 'थ्रेड स्लीप' से पहले कोड में 'बंद करें' को ले जाते हैं, तो आपको 'WaitOne' के दौरान' ऑब्जेक्ट डिस्प्ले अपवाद '("सुरक्षित हैंडल बंद कर दिया गया है) मिलता है। दूसरी तरफ, यदि आप 'थ्रेड' के बाद सीधे 'बंद करें' * को स्थानांतरित करते हैं, तो दोनों थ्रेड सिग्नल हो जाते हैं और निष्पादन सफलतापूर्वक समाप्त हो जाता है। तो, अपरिभाषित व्यवहार, "खराब" कोड में दौड़ की स्थिति है। मैं बस यह सुनिश्चित करना चाहता हूं कि मेरे पास ऐसा कुछ अपरिभाषित व्यवहार न हो जो मुझे लगता है कि मेरा "अच्छा" कोड है। :) – Aaronaught

उत्तर

6

जब आप ManualResetEvent.Set के साथ संकेत करते हैं तो आपको गारंटी है कि उस घटना के लिए इंतजार कर रहे सभी थ्रेड (यानी flag.WaitOne पर अवरुद्ध स्थिति में) कॉलर पर नियंत्रण लौटने से पहले संकेत दिया जाएगा।

बेशक वहाँ हालात हैं जब आप ध्वज सेट कर सकते हैं कर रहे हैं और क्योंकि यह कुछ काम कर रहा है अपने धागा नहीं देखा है इससे पहले कि यह ध्वज की जाँच करता है (या nobugs सुझाव के रूप में यदि आप एक से अधिक थ्रेड बना रहे हैं):

ThreadPool.QueueUserWorkItem(s => 
{ 
    QueryDB(); 
    flag.WaitOne(); 
    Console.WriteLine("Work Item 1 Executed"); 
}); 

ध्वज पर विवाद है और अब आप इसे बंद करते समय अपरिभाषित व्यवहार बना सकते हैं। आपका ध्वज आपके धागे के बीच एक साझा संसाधन है, आपको एक उलटी गिनती लांच बनाना चाहिए जिस पर प्रत्येक धागा पूरा होने पर संकेत करता है। यह आपके flag पर विवाद को खत्म कर देगा।

public class CountdownLatch 
{ 
    private int m_remain; 
    private EventWaitHandle m_event; 

    public CountdownLatch(int count) 
    { 
     Reset(count); 
    } 

    public void Reset(int count) 
    { 
     if (count < 0) 
      throw new ArgumentOutOfRangeException(); 
     m_remain = count; 
     m_event = new ManualResetEvent(false); 
     if (m_remain == 0) 
     { 
      m_event.Set(); 
     } 
    } 

    public void Signal() 
    { 
     // The last thread to signal also sets the event. 
     if (Interlocked.Decrement(ref m_remain) == 0) 
      m_event.Set(); 
    } 

    public void Wait() 
    { 
     m_event.WaitOne(); 
    } 
} 
  1. उलटी गिनती कुंडी पर प्रत्येक धागा संकेत है।
  2. आपका मुख्य धागा उलटी गिनती पर इंतजार कर रहा है।
  3. मुख्य धागा उलटी गिनती के संकेतों के बाद साफ हो जाता है।

आखिरकार जब आप अंत में सोते हैं तो आपकी समस्याओं का ख्याल रखने का एक सुरक्षित तरीका नहीं है, बल्कि आपको अपने प्रोग्राम को डिज़ाइन करना चाहिए ताकि यह एक बहुप्रचारित वातावरण में 100% सुरक्षित हो।

अद्यतन: एकल निर्माता/एकाधिक उपभोक्ताओं
अनुमान यहाँ निर्माता जानता है कि कितने उपभोक्ताओं को बनाया जाएगा आप अपने सभी उपभोक्ताओं को बनाने के बाद, उपभोक्ताओं की दी गई संख्या के साथ CountdownLatch रीसेट है:

// In the Producer 
ManualResetEvent flag = new ManualResetEvent(false); 
CountdownLatch countdown = new CountdownLatch(0); 
int numConsumers = 0; 
while(hasMoreWork) 
{ 
    Consumer consumer = new Consumer(coutndown, flag); 
    // Create a new thread with each consumer 
    numConsumers++; 
} 
countdown.Reset(numConsumers); 
flag.Set(); 
countdown.Wait();// your producer waits for all of the consumers to finish 
flag.Close();// cleanup 
+0

ऐसा लगता है कि यह अवधारणा में एक बेहतर विकल्प हो सकता है; मुझे यह समझने में परेशानी हो रही है कि यह एकाधिक उपभोक्ताओं के साथ एक निर्माता की स्थिति पर कैसे लागू होता है (ऐसा लगता है कि यह कई उत्पादकों, एकल उपभोक्ता के प्रति तैयार है)। क्या आप आगे बता सकते हैं कि इस परिदृश्य में इस लच का उपयोग कैसे किया जाएगा? – Aaronaught

+0

मैंने एकल निर्माता/एकाधिक उपभोक्ता स्थिति को प्रतिबिंबित करने के लिए अपना उत्तर अपडेट कर दिया है। – Kiril

+0

ठीक है, समझ गया। घटना * और * लोच का उपयोग करना है। दुर्भाग्यवश इस मामले में "निर्माता" को पता नहीं होगा कि कितने उपभोक्ता होंगे, वास्तविकता परीक्षण कोड से कहीं अधिक जटिल है ... लेकिन मैं इसे किसी भी तरह अनुकूलित करने में सक्षम हो सकता हूं। अगर मैं थोड़ी देर के बाद सबसे अच्छा जवाब मानता हूं तो मैं इसे स्वीकार करूंगा। – Aaronaught

5

यह ठीक नहीं है। आप यहां भाग्यशाली हो रहे हैं क्योंकि आपने केवल दो धागे शुरू किए हैं। जब आप दोहरी कोर मशीन पर सेट करते हैं तो वे तुरंत चलना शुरू कर देंगे। इस के बजाय का प्रयास करें और यह बम घड़ी:

static void Main(string[] args) { 
     ManualResetEvent flag = new ManualResetEvent(false); 
     for (int ix = 0; ix < 10; ++ix) { 
      ThreadPool.QueueUserWorkItem(s => { 
       flag.WaitOne(); 
       Console.WriteLine("Work Item Executed"); 
      }); 
     } 
     Thread.Sleep(1000); 
     flag.Set(); 
     flag.Close(); 
     Console.WriteLine("Finished"); 
     Console.ReadLine(); 
    } 

आपका मूल कोड एक पुरानी मशीन या अपने वर्तमान मशीन पर इसी तरह विफल हो जाएगा जब यह अन्य कार्यों के साथ बहुत व्यस्त है।

+4

यह पूरी तरह से सही है, लेकिन यह 'कोड' के व्यवहार की वजह से परीक्षण कोड की वजह से बमबारी है। इस मामले में, कुछ थ्रेडों से भी * शुरू * चलने से पहले ईवेंट बंद हो रहा है। 5 सेकंड तक नींद के समय को बढ़ाने से यह क्वाड कोर पर ठीक हो जाता है। मैं घटना के उपयोग को क्रमबद्ध करके उपरोक्त परिदृश्य को रोक सकता हूं और इसे बंद करने के तुरंत बाद इसे निरस्त कर सकता हूं; मैं मुख्य रूप से धागे से चिंतित हूं जो पहले से ही प्रतीक्षा संभाल पर इंतजार कर रहे हैं। – Aaronaught

+0

कोई गारंटी नहीं है कि एक थ्रेड वास्तव में एक मनमानी नींद के बाद चलना शुरू कर देगा। लंबे समय तक सोना केवल ऑब्जेक्ट डिस्प्ले अपवाद का खतरा कम कर देता है, यह इसे खत्म नहीं कर सकता है। इस प्रकार की समय निर्भरता थ्रेडिंग के बारे में धारणा बनाने वाले किसी के ताबूत में अंतिम नाखून है। –

+0

बेशक मैं उत्पादन कोड में 'थ्रेड स्लीप' का उपयोग नहीं करता - वास्तविक कार्यान्वयन ने विभिन्न सिंक प्राइमेटिव का उपयोग किया। लेकिन वहां एक सूक्ष्म दौड़ की स्थिति बन गई, जिसे अब तय कर दिया गया है। आपके सहयोग के लिए धन्यवाद; इसे कॉल करने वाले पहले व्यक्ति होने के लिए +1। – Aaronaught

0

मेरा लेना यह है कि दौड़ की स्थिति है। लिखित ईवेंट की शर्त चर के आधार पर वस्तुओं के बाद, आप इस तरह कोड प्राप्त:

mutex.lock(); 
while (!signalled) 
    condition_variable.wait(mutex); 
mutex.unlock(); 

इसलिए जब घटना का संकेत हो सकता है, कोड घटना पर इंतजार अभी भी घटना के कुछ हिस्सों तक पहुंच की आवश्यकता हो सकती है।

Close पर प्रलेखन के अनुसार, यह केवल अप्रबंधित संसाधनों को रिलीज़ करता है। इसलिए यदि ईवेंट केवल प्रबंधित संसाधनों का उपयोग करता है, तो आप भाग्यशाली हो सकते हैं। लेकिन भविष्य में यह बदल सकता है, इसलिए मैं सावधानी के पक्ष में गलती करूंगा और घटना को बंद नहीं करूँगा जबतक कि आपको पता न हो कि अब इसका उपयोग नहीं किया जा रहा है।

0

मेरे लिए एक जोखिम भरा पैटर्न जैसा दिखता है, भले ही [वर्तमान] कार्यान्वयन के कारण, यह ठीक है। आप एक संसाधन का निपटान करने की कोशिश कर रहे हैं जो अभी भी उपयोग में हो सकता है।

यह ऑब्जेक्ट के उपभोक्ताओं के समक्ष होने से पहले भी एक वस्तु बनाने और निर्माण करने की तरह है और इसे अंधा कर रहा है।

अन्यथा यहां problem भी है। कार्यक्रम से बाहर निकल सकता है, इससे पहले कि अन्य धागे को कभी भी चलाने का मौका मिले। थ्रेड पूल धागे पृष्ठभूमि धागे हैं।

यह देखते हुए कि आपको अन्य धागे के लिए इंतजार करना है, फिर भी आप बाद में साफ कर सकते हैं।