लॉक स्टेटमेंट के अंदर मुझे कितना काम करना चाहिए?


27

मैं एक जूनियर डेवलपर हूं जो सॉफ्टवेयर के लिए एक अद्यतन लिखने पर काम कर रहा है जो एक तृतीय-पक्ष समाधान से डेटा प्राप्त करता है, इसे एक डेटाबेस में संग्रहीत करता है, और फिर एक अन्य तृतीय-पक्ष समाधान द्वारा उपयोग के लिए डेटा को स्थिति देता है। हमारा सॉफ्टवेयर विंडोज सेवा के रूप में चलता है।

पिछले संस्करण के कोड को देखते हुए, मैं इसे देखता हूं:

        static Object _workerLocker = new object();
        static int _runningWorkers = 0;
        int MaxSimultaneousThreads = 5;

        foreach(int SomeObject in ListOfObjects)
        {
            lock (_workerLocker)
            {
                while (_runningWorkers >= MaxSimultaneousThreads)
                {
                    Monitor.Wait(_workerLocker);
                }
            }

            // check to see if the service has been stopped. If yes, then exit
            if (this.IsRunning() == false)
            {
                break;
            }

            lock (_workerLocker)
            {
                _runningWorkers++;
            }

            ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);

        }

तर्क स्पष्ट लगता है: थ्रेड पूल में कमरे के लिए प्रतीक्षा करें, सुनिश्चित करें कि सेवा बंद नहीं की गई है, फिर थ्रेड काउंटर को बढ़ाएं और काम को कतारबद्ध करें। एक बयान है कि तब कॉल के _runningWorkersअंदर SomeMethod()अंदर decremented है ।lockMonitor.Pulse(_workerLocker)

मेरा प्रश्न यह है कि क्या किसी एक के अंदर सभी कोड को समूहीकृत करने में कोई लाभ है lock, जैसे:

        static Object _workerLocker = new object();
        static int _runningWorkers = 0;
        int MaxSimultaneousThreads = 5;

        foreach (int SomeObject in ListOfObjects)
        {
            // Is doing all the work inside a single lock better?
            lock (_workerLocker)
            {
                // wait for room in ThreadPool
                while (_runningWorkers >= MaxSimultaneousThreads) 
                {
                    Monitor.Wait(_workerLocker);
                }
                // check to see if the service has been stopped.
                if (this.IsRunning())
                {
                    ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
                    _runningWorkers++;                  
                }
                else
                {
                    break;
                }
            }
        }

ऐसा लगता है, यह अन्य थ्रेड्स के लिए थोड़ा और इंतजार कर सकता है, लेकिन फिर ऐसा लगता है कि एक लॉजिकल ब्लॉक में बार-बार लॉक करना भी कुछ समय लेने वाला होगा। हालाँकि, मैं मल्टी-थ्रेडिंग में नया हूँ, इसलिए मैं मान रहा हूँ कि यहाँ अन्य चिंताएँ हैं जिनसे मैं अनजान हूँ।

केवल अन्य स्थान जहां _workerLockerलॉक हो जाता है SomeMethod(), और केवल डिक्रिप्टिंग के उद्देश्य से है _runningWorkers, और फिर लॉग आउट करने और लौटने से पहले शून्य पर जाने foreachकी संख्या के _runningWorkersलिए प्रतीक्षा करना है ।

किसी भी मदद के लिए धन्यवाद।

EDIT 4/8/15

अर्धविराम का उपयोग करने के लिए सिफारिश के लिए @delnan का धन्यवाद। कोड बन जाता है:

        static int MaxSimultaneousThreads = 5;
        static Semaphore WorkerSem = new Semaphore(MaxSimultaneousThreads, MaxSimultaneousThreads);

        foreach (int SomeObject in ListOfObjects)
        {
            // wait for an available thread
            WorkerSem.WaitOne();

            // check if the service has stopped
            if (this.IsRunning())
            {
                ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
            }
            else
            {
                break;
            }
        }

WorkerSem.Release()अंदर कहा जाता है SomeMethod()


1
यदि पूरे ब्लॉक को बंद कर दिया जाता है, तो कुछ मेथोड कैसे _reunningWorkers को कम करने के लिए लॉक प्राप्त करेगा?
आईएससी

@RussellatISC: ThreadPool.QueueUserWorkItem SomeMethodअसिंक्रोनस रूप से कॉल करता है , ऊपर दिया गया "लॉक" अनुभाग नए थ्रेड के SomeMethodचलने से पहले या कम से कम शीघ्र ही छोड़ दिया जाएगा ।
डॉक ब्राउन

अच्छी बात। यह मेरी समझ है कि Monitor.Wait()ताला जारी करने और पुन: प्राप्त करने का उद्देश्य एक और संसाधन है (इसलिए SomeMethod, इस मामले में) इसका उपयोग कर सकते हैं। दूसरे छोर पर, SomeMethodलॉक प्राप्त करता है, काउंटर को घटाता है, और फिर कॉल करता है Monitor.Pulse()जो प्रश्न में विधि को लॉक लौटाता है। फिर, यह मेरी अपनी समझ है।
जोसफ

@ डॉक, वह चूक गया, लेकिन फिर भी ... ऐसा लगता है कि कुछ मेथोड को अगले पुनरावृत्ति पर बंद होने से पहले शुरू करने की आवश्यकता होगी या इसे अभी भी आयोजित लॉक के खिलाफ लटका दिया जाएगा "जबकि (_runningWorkers> = MaxSimultaneoushhreads)"।
आईएससी

@RussellatISC: जैसा कि जोसेफ ने पहले ही कहा था: Monitor.Waitताला जारी किया। मैं डॉक्स पर एक नजर डालने की सलाह देता हूं।
डॉक्टर ब्राउन

जवाबों:


33

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

के अंत के बीच while (_runningWorkers >= MaxSimultaneousThreads)और _runningWorkers++, सभी में कुछ भी हो सकता है , क्योंकि कोड आत्मसमर्पण और बीच में ताला फिर से प्राप्त कर लेता है। उदाहरण के लिए, थ्रेड ए पहली बार लॉक का अधिग्रहण कर सकता है, तब तक प्रतीक्षा करें जब तक कि कोई अन्य धागा बाहर न निकल जाए, और फिर लूप-ए से बाहर निकल जाए lock। यह तब पूर्वनिर्मित है, और थ्रेड बी तस्वीर में प्रवेश करता है, थ्रेड पूल में कमरे की प्रतीक्षा भी करता है। क्योंकि कहा अन्य धागा छोड़ने, वहाँ है कक्ष तो यह सब पर बहुत लंबा इंतजार नहीं करता है। थ्रेड ए और थ्रेड बी दोनों अब किसी क्रम में चलते हैं, प्रत्येक वृद्धि _runningWorkersऔर अपना काम शुरू करते हैं।

अब, जहां तक ​​मैं देख सकता हूं, वहां कोई डेटा दौड़ नहीं है, लेकिन तार्किक रूप से यह गलत है, क्योंकि अब MaxSimultaneousThreadsश्रमिकों को चलाने की तुलना में अधिक हैं । चेक (कभी-कभी) अप्रभावी होता है क्योंकि थ्रेड पूल में स्लॉट लेने का कार्य परमाणु नहीं है। यह आपको लॉक ग्रैन्युलैरिटी के आस-पास की छोटी अनुकूलन से अधिक चिंता करना चाहिए! (ध्यान दें कि इसके विपरीत, बहुत जल्दी या बहुत लंबे समय तक लॉक करना आसानी से गतिरोध पैदा कर सकता है।)

दूसरा स्निपेट इस समस्या को ठीक करता है, जहां तक ​​मैं देख सकता हूं। समस्या को ठीक करने के लिए एक कम आक्रामक परिवर्तन पहले लॉक स्टेटमेंट के अंदर, लुक के ++_runningWorkersबाद अधिकार डाल सकता है while

अब, एक तरफ शुद्धता, प्रदर्शन के बारे में क्या? यह बताना कठिन है। आम तौर पर लंबे समय तक ("मोटे तौर पर") ताला लगाना संगामिति को रोकता है, लेकिन जैसा कि आप कहते हैं, इसके लिए ठीक-ठाक लॉकिंग के अतिरिक्त सिंक्रनाइज़ेशन से ओवरहेड के खिलाफ संतुलित होना चाहिए। आम तौर पर एकमात्र समाधान बेंचमार्किंग है और यह जानते हुए कि "हर जगह सब कुछ लॉक करें" और "केवल नंगे न्यूनतम लॉक" से अधिक विकल्प हैं। वहाँ पैटर्न और संगामिति आदिम और धागा सुरक्षित डेटा संरचनाओं का खजाना उपलब्ध है। उदाहरण के लिए, ऐसा लगता है जैसे बहुत ही अनुप्रयोग सेमाफोर का आविष्कार किया गया था, इसलिए इस हाथ से लुढ़के हुए काउंटर के बजाय उन में से एक का उपयोग करने पर विचार करें।


11

IMHO आप गलत प्रश्न पूछ रहे हैं - आपको दक्षता व्यापार-नापसंद के बारे में इतना ध्यान नहीं देना चाहिए, लेकिन शुद्धता के बारे में अधिक।

पहला वेरिएंट सुनिश्चित करता है कि _runningWorkersकेवल एक लॉक के दौरान एक्सेस किया जाता है, लेकिन यह उस मामले को याद करता है जहां _runningWorkersपहले लॉक और दूसरे के बीच के अंतर में एक और धागा द्वारा बदला जा सकता है। ईमानदारी से, कोड मुझे दिखता है अगर किसी ने _runningWorkersनिहितार्थ और संभावित त्रुटियों के बारे में सोचने के बिना सभी एक्सेस बिंदुओं के चारों ओर नेत्रहीन ताले लगा दिए हैं। शायद लेखक को ब्लॉक के breakअंदर बयान को निष्पादित करने के बारे में कुछ अंधविश्वास था lock, लेकिन कौन जानता है?

इसलिए आपको वास्तव में दूसरे संस्करण का उपयोग करना चाहिए, इसलिए नहीं कि यह अधिक या कम कुशल है, बल्कि इसलिए कि यह (उम्मीद है) पहले वाले से अधिक सही है।


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

@ सुपरकैट: यहाँ ऐसा नहीं है, कृपया मूल प्रश्न के नीचे टिप्पणी पढ़ें।
डॉक ब्राउन

9

अन्य उत्तर काफी अच्छे हैं और स्पष्ट रूप से शुद्धता की चिंताओं को दूर करते हैं। मुझे आपके सामान्य प्रश्न का समाधान करने दें:

लॉक स्टेटमेंट के अंदर मुझे कितना काम करना चाहिए?

आइए मानक सलाह के साथ शुरू करते हैं, जिसे आप स्वीकार किए जाते हैं और स्वीकृत उत्तर के अंतिम पैराग्राफ में सभी को चित्रित करते हैं:

  • किसी विशेष वस्तु को लॉक करते समय जितना संभव हो उतना कम काम करें। एक लंबे समय के लिए आयोजित ताले विवाद के अधीन हैं, और विवाद धीमा है। नोट यह संकेत मिलता है कि कि कुल एक में कोड की राशि विशेष रूप से ताला और कुल में कोड की राशि सभी ताला बयान है कि एक ही वस्तु पर ताला दोनों प्रासंगिक हैं।

  • संभव के रूप में कुछ ताले हैं, गतिरोध (या livelocks) की संभावना कम करने के लिए।

चतुर पाठक ध्यान देगा कि ये विपरीत हैं। पहला बिंदु विवाद से बचने के लिए कई छोटे, महीन दाने वाले ताले में बड़े ताले को तोड़ने का सुझाव देता है। दूसरा गतिरोध से बचने के लिए एक ही लॉक ऑब्जेक्ट में अलग-अलग लॉक को समेकित करने का सुझाव देता है।

हम इस तथ्य से क्या निष्कर्ष निकाल सकते हैं कि सर्वोत्तम मानक सलाह पूरी तरह से विरोधाभासी है? हमें वास्तव में अच्छी सलाह मिलती है:

  • पहली जगह में वहाँ मत जाओ। यदि आप थ्रेड्स के बीच मेमोरी साझा कर रहे हैं, तो आप दर्द की दुनिया के लिए खुद को खोल रहे हैं।

मेरी सलाह है, यदि आप समसामयिकता चाहते हैं, तो अपनी संगणना की इकाई के रूप में प्रक्रियाओं का उपयोग करें। यदि आप प्रक्रियाओं का उपयोग नहीं कर सकते हैं तो एप्लिकेशन डोमेन का उपयोग करें। यदि आप एप्लिकेशन डोमेन का उपयोग नहीं कर सकते हैं, तो आपके धागे टास्क पैरेलल लाइब्रेरी द्वारा प्रबंधित किए जाते हैं और निम्न-स्तरीय थ्रेड्स (श्रमिकों) के बजाय उच्च-स्तरीय कार्यों (नौकरियों) के संदर्भ में अपना कोड लिखते हैं।

यदि आप बिल्कुल सकारात्मक रूप से थ्रेड्स या सेमाफोर जैसे निम्न-स्तरीय संगामिति प्राइमेटिव्स का उपयोग करते हैं , तो उन्हें उच्च-स्तरीय एब्सट्रैक्शन बनाने के लिए उपयोग करें जो आपको वास्तव में जो चाहिए उसे कैप्चर करता है। आपको पता चलेगा कि उच्च स्तर का अमूर्त कुछ ऐसा है जैसे "कार्य को अतुल्य रूप से निष्पादित करें जिसे उपयोगकर्ता द्वारा रद्द किया जा सकता है", और हे, टीपीएल पहले से ही इसका समर्थन करता है, इसलिए आपको अपना रोल करने की आवश्यकता नहीं है। आप संभावना पाएंगे कि आपको थ्रेड सुरक्षित आलसी आरंभीकरण जैसी कोई चीज़ चाहिए; अपने स्वयं के रोल न करें, उपयोग करें Lazy<T>, जो विशेषज्ञों द्वारा लिखा गया था। विशेषज्ञों द्वारा लिखित थ्रेडसेफ़ संग्रह (अपरिवर्तनीय या अन्यथा) का उपयोग करें। अमूर्त के स्तर को जितना संभव हो उतना ऊपर ले जाएं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.