2009-07-15 12 views
34

मेरे पास एक बहुप्रचारित प्रोग्राम है जहां मैं जनरेटर फ़ंक्शन बनाता हूं और फिर इसे नए धागे में भेजता हूं। मैं इसे प्रकृति में साझा/ग्लोबल करना चाहता हूं ताकि प्रत्येक धागा जनरेटर से अगला मूल्य प्राप्त कर सके।जेनरेटर थ्रेडसेफ हैं?

क्या इस तरह के जनरेटर का उपयोग करना सुरक्षित है, या क्या मैं एकाधिक जेनर से साझा जनरेटर तक पहुंचने वाली समस्याओं/शर्तों में भाग लेगा?

यदि नहीं, तो समस्या से निपटने का एक बेहतर तरीका है? मुझे कुछ ऐसी चीज चाहिए जो एक सूची के माध्यम से चक्र करेगी और जो भी थ्रेड इसे कॉल करे, उसके लिए अगले मूल्य का उत्पादन करेगी।

उत्तर

49

यह थ्रेड-सुरक्षित नहीं है; एक साथ कॉल स्थानीय चर के साथ interleave, और गड़बड़ कर सकते हैं।

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

+7

निश्चित रूप से Queue.Queue के लिए +1, लागू होने पर थ्रेडिंग सिस्टम को व्यवस्थित करने का शानदार तरीका (जो अधिकतर समय है, और निश्चित रूप से इस कार्य के लिए)। –

-7

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

http://en.wikipedia.org/wiki/Global_Interpreter_Lock

+1

"जीआईएल पाइथन ऑब्जेक्ट्स थ्रेडसेफ पर सभी परिचालन करता है" - हुह? सभी परिचालन परमाणु नहीं हैं –

+6

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

40

संपादित नीचे बेंचमार्क जोड़ने के लिए।

आप एक जेनरेटर को लॉक से लपेट सकते हैं। उदाहरण के लिए,

import threading 
class LockedIterator(object): 
    def __init__(self, it): 
     self.lock = threading.Lock() 
     self.it = it.__iter__() 

    def __iter__(self): return self 

    def next(self): 
     self.lock.acquire() 
     try: 
      return self.it.next() 
     finally: 
      self.lock.release() 

gen = [x*2 for x in [1,2,3,4]] 
g2 = LockedIterator(gen) 
print list(g2) 

लॉकिंग अपने सिस्टम पर 50ms लेता है, कतार 350ms लेता है। जब आप वास्तव में एक कतार है तो कतार उपयोगी होती है; उदाहरण के लिए, यदि आपके पास आने वाले HTTP अनुरोध हैं और आप उन्हें वर्कर थ्रेड द्वारा प्रोसेसिंग के लिए कतार देना चाहते हैं। (यह पाइथन इटेटर मॉडल में फिट नहीं होता है - एक बार इटेटरेटर आइटम से बाहर हो जाता है, यह हो जाता है।) यदि आपके पास वास्तव में एक इटरेटर है, तो लॉकइटरेटर इसे थ्रेड सुरक्षित बनाने का एक तेज़ और आसान तरीका है।

from datetime import datetime 
import threading 
num_worker_threads = 4 

class LockedIterator(object): 
    def __init__(self, it): 
     self.lock = threading.Lock() 
     self.it = it.__iter__() 

    def __iter__(self): return self 

    def next(self): 
     self.lock.acquire() 
     try: 
      return self.it.next() 
     finally: 
      self.lock.release() 

def test_locked(it): 
    it = LockedIterator(it) 
    def worker(): 
     try: 
      for i in it: 
       pass 
     except Exception, e: 
      print e 
      raise 

    threads = [] 
    for i in range(num_worker_threads): 
     t = threading.Thread(target=worker) 
     threads.append(t) 
     t.start() 

    for t in threads: 
     t.join() 

def test_queue(it): 
    from Queue import Queue 
    def worker(): 
     try: 
      while True: 
       item = q.get() 
       q.task_done() 
     except Exception, e: 
      print e 
      raise 

    q = Queue() 
    for i in range(num_worker_threads): 
     t = threading.Thread(target=worker) 
     t.setDaemon(True) 
     t.start() 

    t1 = datetime.now() 

    for item in it: 
     q.put(item) 

    q.join() 

start_time = datetime.now() 
it = [x*2 for x in range(1,10000)] 

test_locked(it) 
#test_queue(it) 
end_time = datetime.now() 
took = end_time-start_time 
print "took %.01f" % ((took.seconds + took.microseconds/1000000.0)*1000) 
+1

एक कतार का उपयोग कर कम कुशल। क्यूई, लेकिन खूबसूरती से किया। – gooli

संबंधित मुद्दे