एक धागे के साथ एक वैश्विक चर का उपयोग करना


84

मैं धागे के साथ एक वैश्विक चर कैसे साझा करूं?

मेरा पायथन कोड उदाहरण है:

from threading import Thread
import time
a = 0  #global variable

def thread1(threadname):
    #read variable "a" modify by thread 2

def thread2(threadname):
    while 1:
        a += 1
        time.sleep(1)

thread1 = Thread( target=thread1, args=("Thread-1", ) )
thread2 = Thread( target=thread2, args=("Thread-2", ) )

thread1.join()
thread2.join()

मुझे नहीं पता कि एक चर साझा करने के लिए दो धागे कैसे मिलते हैं।

जवाबों:


97

आपको बस aएक वैश्विक के रूप में घोषित करने की आवश्यकता है thread2, ताकि आप aउस फ़ंक्शन को स्थानीय रूप से संशोधित न कर सकें ।

def thread2(threadname):
    global a
    while True:
        a += 1
        time.sleep(1)

जब thread1तक आप मूल्य को संशोधित करने का प्रयास नहीं करते a(जो कि एक स्थानीय वैरिएबल बनाता है जो वैश्विक एक को छाया देता है, तब तक उपयोग global aकरने की आवश्यकता नहीं है ; यदि आपको आवश्यकता है) का उपयोग करें >

def thread1(threadname):
    #global a       # Optional if you treat a as read-only
    while a < 10:
        print a

47

एक समारोह में:

a += 1

संकलक द्वारा व्याख्या की जाएगी assign to a => Create local variable a, जैसा कि आप जो चाहते हैं वह नहीं है। यह संभवतः एक a not initializedत्रुटि के साथ विफल हो जाएगा क्योंकि (स्थानीय) वास्तव में प्रारंभ नहीं किया गया है:

>>> a = 1
>>> def f():
...     a += 1
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'a' referenced before assignment

आपको वह मिल सकता है जो आप चाहते हैं (बहुत अच्छे और अच्छे कारणों से) globalकीवर्ड, जैसे:

>>> def f():
...     global a
...     a += 1
... 
>>> a
1
>>> f()
>>> a
2

सामान्य तौर पर, आपको वैश्विक चर का उपयोग करने से बचना चाहिए जो बहुत जल्दी हाथ से निकल जाते हैं। और यह बहुस्तरीय कार्यक्रमों के लिए विशेष रूप से सच है, जहां आपके पास संशोधित किए thread1जाने पर आपके लिए कोई भी सिंक्रनाइज़ेशन तंत्र नहीं है a। संक्षेप में: थ्रेड्स जटिल होते हैं , और आप उस क्रम की एक सहज समझ होने की उम्मीद नहीं कर सकते हैं, जिसमें दो (या अधिक) थ्रेड्स एक ही मान पर कार्य करते समय घटनाएँ हो रही हैं। भाषा, संकलक, OS, प्रोसेसर ... सभी एक भूमिका निभा सकते हैं, और गति, व्यावहारिकता या किसी अन्य कारण से संचालन के क्रम को संशोधित करने का निर्णय ले सकते हैं।

इस तरह की चीज़ों के लिए उचित तरीका है पायथन शेयरिंग टूल्स ( ताले और दोस्त) का उपयोग करना, या बेहतर, इसे साझा करने के बजाय क्यू के माध्यम से डेटा का संचार करना, जैसे कि:

from threading import Thread
from queue import Queue
import time

def thread1(threadname, q):
    #read variable "a" modify by thread 2
    while True:
        a = q.get()
        if a is None: return # Poison pill
        print a

def thread2(threadname, q):
    a = 0
    for _ in xrange(10):
        a += 1
        q.put(a)
        time.sleep(1)
    q.put(None) # Poison pill

queue = Queue()
thread1 = Thread( target=thread1, args=("Thread-1", queue) )
thread2 = Thread( target=thread2, args=("Thread-2", queue) )

thread1.start()
thread2.start()
thread1.join()
thread2.join()

यह एक बड़ी समस्या हल करता है। और बहुत अच्छा लगता है सही तरीका यह जाना है।
अभिमन्यु

इस तरह से मैं सिंक्रनाइज़ेशन समस्या को हल करने के लिए उपयोग कर रहा हूं।
झांग लोंगक्यूआई

1
मेरे पास कुछ प्रश्न हैं। यदि मेरे पास थ्रेड्स के बीच साझा करने के लिए कई चर हैं, तो क्या मुझे प्रत्येक चर के लिए एक अलग कतार की आवश्यकता है? दूसरा, उपरोक्त कार्यक्रम में कतारों को क्यों समन्वयित किया गया है? क्या प्रत्येक फ़ंक्शन में स्थानीय प्रति के रूप में प्रत्येक कार्य नहीं होना चाहिए?

यह पुराना है, लेकिन मैं वैसे भी जवाब देता हूं। कतार स्वयं सिंक्रनाइज़ नहीं है, चर से अधिक नहीं है a। यह कतार का डिफ़ॉल्ट अवरुद्ध व्यवहार है जो सिंक्रनाइज़ेशन बनाता है। a = q.get()जब तक कोई मान उपलब्ध नहीं हो जाता तब तक स्टेटमेंट ब्लॉक (प्रतीक्षा) होगा। चर qस्थानीय है: यदि आप इसके लिए एक अलग मूल्य प्रदान करते हैं, तो यह स्थानीय रूप से ही होगा। लेकिन कोड में इसे दी गई कतार वह है जिसे मुख्य धागे में परिभाषित किया गया है।

1
हमेशा धागे के बीच जानकारी साझा करने के लिए एक कतार का उपयोग करना आवश्यक नहीं है। चेपनर जवाब में उदाहरण पूरी तरह से ठीक है। इसके अलावा, एक कतार हमेशा सही उपकरण नहीं होती है। एक कतार उपयोगी है, उदाहरण के लिए, यदि आप मूल्य उपलब्ध होने तक ब्लॉक करना चाहते हैं। यदि दो धागे एक साझा संसाधन पर प्रतिस्पर्धा करते हैं तो यह बेकार है। अंत में, वैश्विक चर धागे में सबसे खराब नहीं हैं। वे वास्तव में अधिक प्राकृतिक हो सकते हैं। उदाहरण के लिए, आपका धागा कोड का एक ब्लॉक हो सकता है, एक लूप कह सकता है, जिसे अपनी प्रक्रिया की आवश्यकता है। जब आप किसी फ़ंक्शन में लूप डालते हैं तो स्थानीय स्कोप इस प्रकार कृत्रिम रूप से बनाया जाता है।

5

एक लॉक का उपयोग करने पर विचार किया जाना चाहिए, जैसे कि threading.Lock। अधिक जानकारी के लिए लॉक-ऑब्जेक्ट देखें ।

स्वीकृत उत्तर थ्रेड 1 द्वारा 10 प्रिंट कर सकता है, जो आप नहीं चाहते हैं। बग को अधिक आसानी से समझने के लिए आप निम्न कोड चला सकते हैं।

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print "unreachable."

def thread2(threadname):
    global a
    while True:
        a += 1

एक aसे अधिक बार पढ़ने के दौरान लॉक को बदलने से मना करना

def thread1(threadname):
    while True:
      lock_a.acquire()
      if a % 2 and not a % 2:
          print "unreachable."
      lock_a.release()

def thread2(threadname):
    global a
    while True:
        lock_a.acquire()
        a += 1
        lock_a.release()

यदि लंबे समय तक चर का उपयोग करते हुए धागा, तो इसे पहले स्थानीय चर पर कॉपी करना एक अच्छा विकल्प है।


3

उस विधि का सुझाव देने के लिए बहुत बहुत धन्यवाद जेसन पैन। थ्रेड 1 यदि कथन परमाणु नहीं है, तो जबकि वह कथन निष्पादित करता है, थ्रेड 2 के लिए थ्रेड 1 पर घुसपैठ करना संभव है, गैर-पहुंच योग्य कोड तक पहुंचने की अनुमति देता है। मैंने पूर्व के पदों से विचारों को एक पूर्ण प्रदर्शन कार्यक्रम (नीचे) में आयोजित किया है जिसे मैंने पायथन 2.7 के साथ चलाया था।

कुछ विचारशील विश्लेषण के साथ मुझे यकीन है कि हम आगे अंतर्दृष्टि प्राप्त कर सकते हैं, लेकिन अब मुझे लगता है कि यह प्रदर्शित करना महत्वपूर्ण है कि जब गैर-परमाणु व्यवहार प्रसार से मिलता है तो क्या होता है।

# ThreadTest01.py - Demonstrates that if non-atomic actions on
# global variables are protected, task can intrude on each other.
from threading import Thread
import time

# global variable
a = 0; NN = 100

def thread1(threadname):
    while True:
      if a % 2 and not a % 2:
          print("unreachable.")
    # end of thread1

def thread2(threadname):
    global a
    for _ in range(NN):
        a += 1
        time.sleep(0.1)
    # end of thread2

thread1 = Thread(target=thread1, args=("Thread1",))
thread2 = Thread(target=thread2, args=("Thread2",))

thread1.start()
thread2.start()

thread2.join()
# end of ThreadTest01.py

जैसा कि अनुमान लगाया गया है, उदाहरण चलाने में, "अप्राप्य" कोड कभी-कभी वास्तव में पहुंच जाता है, उत्पादन का उत्पादन करता है।

बस जोड़ने के लिए, जब मैंने थ्रेड 1 में लॉक अधिग्रहित / रिलीज जोड़ी डाली तो मैंने पाया कि "अगम्य" संदेश प्रिंट होने की संभावना बहुत कम हो गई थी। संदेश देखने के लिए मैंने नींद के समय को 0.01 सेकंड तक कम कर दिया और NN को बढ़ाकर 1000 कर दिया।

थ्रेड 1 में लॉक अधिग्रहण / रिलीज जोड़ी के साथ मुझे संदेश देखने की उम्मीद नहीं थी, लेकिन यह वहां है। जब मैंने एक लॉक अधिग्रहण / रिलीज़ जोड़ी को थ्रेड 2 में सम्मिलित किया, उसके बाद संदेश दिखाई नहीं दिया। हिंद संकेत में, थ्रेड 2 में वृद्धि का बयान शायद गैर-परमाणु भी है।


1
आपको दोनों थ्रेड्स में ताले की आवश्यकता है क्योंकि ये सहकारी, "सलाहकार ताले" ("अनिवार्य" नहीं हैं)। आप सही हैं कि वेतन वृद्धि गैर-परमाणु कथन है।
Darkonaut

1

खैर, चल रहा उदाहरण:

चेतावनी! कभी घर पर काम करते हैं / काम करते हैं! केवल कक्षा में;)

भीड़ की स्थिति से बचने के लिए सेमाफोर, साझा चर आदि का उपयोग करें।

from threading import Thread
import time

a = 0  # global variable


def thread1(threadname):
    global a
    for k in range(100):
        print("{} {}".format(threadname, a))
        time.sleep(0.1)
        if k == 5:
            a += 100


def thread2(threadname):
    global a
    for k in range(10):
        a += 1
        time.sleep(0.2)


thread1 = Thread(target=thread1, args=("Thread-1",))
thread2 = Thread(target=thread2, args=("Thread-2",))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

और आउटपुट:

Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 2
Thread-1 3
Thread-1 3
Thread-1 104
Thread-1 104
Thread-1 105
Thread-1 105
Thread-1 106
Thread-1 106
Thread-1 107
Thread-1 107
Thread-1 108
Thread-1 108
Thread-1 109
Thread-1 109
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110
Thread-1 110

यदि समय सही था, तो a += 100ऑपरेशन को छोड़ दिया जाएगा:

प्रोसेसर टी पर निष्पादित होता है a+100और 104 हो जाता है। लेकिन यह बंद हो जाता है, और अगले धागे पर कूदता है यहां, a+1ए + के पुराने मूल्य के साथ टी + 1 निष्पादित करता है a == 4। तो यह गणना करता है 5. वापस कूदो (टी + 2 पर), धागा 1, और a=104स्मृति में लिखें । अब वापस थ्रेड 2 पर, समय T + 3 है और a=5मेमोरी में लिखें । देखा! अगला प्रिंट निर्देश 104 के बजाय 5 प्रिंट करेगा।

बहुत बुरा बग प्रजनन और पकड़ा जाना है।


कृपया एक सही कार्यान्वयन जोड़ने पर भी विचार करें। यह उन लोगों के लिए बहुत उपयोगी होगा जो थ्रेड्स के बीच डेटा साझा करना सीखते हैं।
जेएस।

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