2009-05-12 9 views
6

मैंने हाल ही में एक कार्यक्रम लिखा है जो एक साधारण निर्माता/उपभोक्ता पैटर्न का उपयोग करता है। शुरुआत में इसे थ्रेडिंग के अनुचित उपयोग से संबंधित एक बग था। लॉक कि मैंने अंततः तय किया। लेकिन इससे मुझे लगता है कि निर्माता/उपभोक्ता पैटर्न को लॉकलेस तरीके से कार्यान्वित करना संभव है या नहीं।क्या यह पायथन निर्माता-उपभोक्ता लॉकलेस दृष्टिकोण थ्रेड-सुरक्षित है?

  • एक निर्माता धागा: मेरे मामले में

    आवश्यकताओं सरल थे।

  • एक उपभोक्ता धागा।
  • कतार केवल एक आइटम के लिए स्थान नहीं है।
  • निर्माता अगले आइटम से पहले मौजूदा एक सेवन किया जाता है उत्पादन कर सकते हैं। इसलिए वर्तमान आइटम खो गया है, लेकिन यह मेरे लिए ठीक है।
  • उपभोक्ता वर्तमान आइटम उपभोग कर सकते हैं इससे पहले कि अगले एक का उत्पादन किया है। इसलिए वर्तमान वस्तु को दो बार (या अधिक) खपत किया जाता है, लेकिन यह मेरे लिए ठीक है।

तो मैं यह लिखा:

QUEUE_ITEM = None 

# this is executed in one threading.Thread object 
def producer(): 
    global QUEUE_ITEM 
    while True: 
     i = produce_item() 
     QUEUE_ITEM = i 

# this is executed in another threading.Thread object 
def consumer(): 
    global QUEUE_ITEM 
    while True: 
     i = QUEUE_ITEM 
     consume_item(i) 

मेरे सवाल यह है: इस कोड को धागे की सुरक्षित है?

तत्काल टिप्पणी: इस कोड को वास्तव में नहीं है लॉकसेल - मैं CPython उपयोग करें और यह जीआईएल है।

मैं कोड एक छोटे से परीक्षण किया है और यह काम करने लगता है। यह कुछ लोड और स्टोर ओप में अनुवाद करता है जो जीआईएल के कारण परमाणु हैं। लेकिन मुझे यह भी पता है कि del x ऑपरेशन परमाणु नहीं है जब x __del__ विधि लागू करता है। तो अगर मेरे आइटम में __del__ विधि है और कुछ बुरा शेड्यूलिंग होता है, तो चीजें तोड़ सकती हैं। या नहीं?

एक और सवाल यह है कि: किस तरह के प्रतिबंध (उदाहरण के लिए उत्पादित वस्तुओं के प्रकार पर) उदाहरण के लिए मुझे ऊपर कोड को ठीक करने के लिए लगाया जाना चाहिए?

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

+0

आप __del__ विधि क्यों लिखेंगे? –

उत्तर

2

हाँ इस तरह से है कि आप वर्णित में काम करेंगे:

  1. कि निर्माता स्किप करने योग्य तत्व हो सकता है।
  2. कि उपभोक्ता एक ही तत्व का उपभोग कर सकता है।

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

मुझे यहां "डेल" नहीं दिख रहा है। यदि कोई del delume_item में होता है तो del निर्माता थ्रेड में हो सकता है। मुझे नहीं लगता कि यह एक "समस्या" होगी।

हालांकि इसका उपयोग करने से परेशान न हों। आप पॉइंटलेस मतदान चक्रों पर सीपीयू का उपयोग कर समाप्त कर देंगे, और यह ताले के साथ कतार का उपयोग करने से कहीं अधिक तेज़ नहीं है क्योंकि पाइथन के पास पहले से ही वैश्विक लॉक है।

+0

'__del__' द्वारा मेरा मतलब था कि किसी ऑब्जेक्ट की रिफ्रेंस गिनती शून्य हो सकती है और इसलिए यह' __del__' विधि कहलाएगी। इससे कुछ समस्याएं हो सकती हैं, लेकिन यदि आप कहते हैं कि यह ठीक है, तो मुझे उम्मीद है कि चीजें सीपीथन में हैं। – Jasiu

1

इसका कारण यह है निर्माता QUEUE_ITEM के ऊपर लिख सकता है उपभोक्ता से पहले ही उपयोग किए गए और उपभोक्ता दो बार QUEUE_ITEM का उपभोग नहीं कर सका वास्तव में धागा सुरक्षित है। जैसा कि आपने बताया है, आप इसके साथ ठीक हैं लेकिन अधिकांश लोग नहीं हैं।

किसी को भी cpython internals के अधिक ज्ञान के साथ आपको अधिक सैद्धांतिक प्रश्नों का उत्तर देना होगा।

+0

हां, एक तरह से मेरा कोड न तो थ्रेडसेफ है, न ही लॉकलेस। :) यहां 'थ्रेडसेफ' का मेरा मतलब है: क्रैश नहीं होता है, स्मृति को भ्रष्ट नहीं करता है, डेडलॉक में जमा नहीं होता है और मेरी आवश्यकताओं के अनुसार वर्णन करता है। – Jasiu

+0

मेरा मानना ​​है कि जीआईएल आपको उन त्रुटियों के प्रकार से बचाएगा जिन्हें आपने अभी उल्लेख किया है। जीआईएल पाइथन के आंतरिक राज्य को धागे के चेहरे में सही रखने के लिए है। आपका कोड आपके द्वारा अपेक्षित तरीके से व्यवहार नहीं कर सकता है (लेकिन आप मूल रूप से पहले से ही कहा है कि दौड़ की स्थिति आप जो चाहते हैं उसके लिए ठीक है), लेकिन मुझे नहीं लगता कि यह दुभाषिया के दृष्टिकोण से असुरक्षित होगा क्योंकि दुभाषिया की आंतरिक स्थिति है जीआईएल द्वारा संरक्षित – Doug

0

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

इसके अलावा धागे किसी भी संख्या को चलाने से पहले किसी भी संख्या का उत्पादन/उपभोग कर सकते हैं।

+0

यह एक अच्छा मुद्दा है, यह एक संभावना लाता है जिसके बारे में मैंने सोचा नहीं है। लेकिन मैं अपने समाधान की रक्षा करने की कोशिश करूंगा: AFAIK, पायथन एक सिग्नल मास्क के साथ प्रत्येक ऑपोड निष्पादित करता है, ताकि यह बाधित न हो और इसलिए परमाणु हो। अन्यथा चीजें खराब हो जाएंगी, मुझे लगता है, और यहां तक ​​कि नियमित पायथन सामग्री भी बहु-थ्रेडेड काम नहीं करेगी। – Jasiu

0

आप कतार के रूप में एक सूची का उपयोग तब तक कर सकते हैं जब तक कि आप दोनों परमाणु हैं क्योंकि आप संलग्न/पॉप करना चाहते हैं।

QUEUE = [] 

# this is executed in one threading.Thread object 
def producer(): 
    global QUEUE 
    while True: 
     i = produce_item() 
     QUEUE.append(i) 

# this is executed in another threading.Thread object 
def consumer(): 
    global QUEUE 
    while True: 
     try: 
      i = QUEUE.pop(0) 
     except IndexError: 
      # queue is empty 
      continue 

     consume_item(i) 

नीचे की तरह एक वर्ग के दायरे में, आप कतार को भी साफ़ कर सकते हैं।

class Atomic(object): 
    def __init__(self): 
     self.queue = [] 

    # this is executed in one threading.Thread object 
    def producer(self): 
     while True: 
      i = produce_item() 
      self.queue.append(i) 

    # this is executed in another threading.Thread object 
    def consumer(self): 
     while True: 
      try: 
       i = self.queue.pop(0) 
      except IndexError: 
       # queue is empty 
       continue 

      consume_item(i) 

    # There's the possibility producer is still working on it's current item. 
    def clear_queue(self): 
     self.queue = [] 

आपको यह पता लगाना होगा कि बाइटकोड जेनरेट करके आपको कौन सी सूची संचालन परमाणु है।

+0

मुझे संदेह है कि आपने एक प्रश्न से/पॉपिंग करने के लिए एक वैश्विक चर को पढ़ने/लिखने से मेरा प्रश्न अभी स्थानांतरित कर दिया है, लेकिन सवाल बनी हुई है: क्या मेरा या आपका कोड कुछ बुरा शेड्यूलिंग करने पर भी काम करेगा, __del__ को कॉल करना होगा? – Jasiu

+0

स्पष्ट रूप से या डेल कॉल करके __del__ को कॉल करना? डेल तुरंत इसे हटा नहीं देता है। यह सिर्फ संदर्भ गणना को कम करता है। जब तक उपभोक्ता इसका संदर्भ रखता है, यह ठीक है। – null

+0

चलिए निम्नलिखित परिदृश्य पर विचार करें: 1. कतार में कई आइटम हैं। 2. उपभोक्ता clear_queue कॉल करता है। 3. कतार में आइटम की संदर्भ शून्य से ड्रॉप। 4. उनके __del__ विधियों को बुलाया जाता है। 5।यह सब "self.queue = []" कथन के दौरान होता है। 6. इस बीच निर्माता एक और आइटम जोड़ने की कोशिश करता है। आप "self.queue = []" को "del self.queue [:]" से प्रतिस्थापित कर सकते हैं, लेकिन यह समस्या को "self.queue" विशेषता को पायथन के आंतरिक सूची संचालन तक पहुंचने से रोकती है। तो आईएमएचओ यह फिर से समस्या को पाइथन की बिल्टिन सूची आंतरिक पढ़ने/लिखने के लिए ग्लोबल वैरिएबल को पढ़ने/लिखने से प्रेरित करता है। – Jasiu

6

ट्रिकरी आपको काट देगा। थ्रेड के बीच संवाद करने के लिए बस कतार का उपयोग करें।

+0

हां, मैं यही करता हूं! :) मैं उत्पादन वातावरण में ऐसे कोड का उपयोग नहीं करता, कोई रास्ता नहीं :)। यह सिर्फ एक सैद्धांतिक सवाल है :)। – Jasiu

0

__del__ जैसा आपने कहा था एक समस्या हो सकती है। इससे बचा जा सकता है, अगर कचरा कलेक्टर को __del__ विधि को पुराने ऑब्जेक्ट पर विधि से पहले हम QUEUE_ITEM पर नया कार्य करने से पहले ही एक तरीका था। हमें कुछ की आवश्यकता होगी:

increase the reference counter on the old object 
assign a new one to `QUEUE_ITEM` 
decrease the reference counter on the old object 

मुझे डर है, मुझे नहीं पता कि यह संभव है या नहीं।