একটি থ্রেড সহ গ্লোবাল ভেরিয়েবল ব্যবহার করা


86

আমি কীভাবে থ্রেডের সাথে বিশ্বব্যাপী পরিবর্তনশীল ভাগ করব?

আমার পাইথন কোড উদাহরণটি হ'ল:

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()

একটি চলক ভাগ করতে দুটি থ্রেড কীভাবে পাবেন তা আমি জানি না।

উত্তর:


99

আপনাকে কেবলমাত্র 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সংশোধন হয়েছে তা জানার জন্য কোনও সিঙ্ক্রোনাইজেশন প্রক্রিয়া নেই । সংক্ষেপে: থ্রেডগুলি জটিল , এবং যখন দুটি (বা ততোধিক) থ্রেড একই মূল্যে কাজ করে তখন ঘটনাগুলি ক্রম ক্রম সম্পর্কে অন্তর্নিহিত বোঝার আশা করতে পারবেন না। ভাষা, সংকলক, ওএস, প্রসেসর ... সবই ভূমিকা নিতে পারে এবং গতি, ব্যবহারিকতা বা অন্য কোনও কারণে ক্রিয়াকলাপের ক্রমটি সংশোধন করার সিদ্ধান্ত নিতে পারে।

এই জাতীয় জিনিসটির যথাযথ উপায় হল পাইথন ভাগ করে নেওয়ার সরঞ্জামগুলি ( লক এবং বন্ধু) ব্যবহার করা, বা আরও ভাল, কোনও সারির মাধ্যমে ডেটা ভাগ করার পরিবর্তে ডেটা যোগাযোগ করা, যেমন:

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()

এটি একটি বড় সমস্যা সমাধান করে। এবং এটি প্রশংসনীয়ভাবে ডানদিকে যাওয়ার জন্য সঠিক পদ্ধতির থেকে মনে হচ্ছে।
অভিদেমন

সিঙ্ক্রোনাইজেশন সমস্যা সমাধানের জন্য আমি এইভাবেই ব্যবহার করছি।
ঝাং লংকিউআই

4
আমার কিছু প্রশ্ন রয়েছে irst প্রথম, থ্রেডগুলির মধ্যে ভাগ করার জন্য আমার যদি একাধিক ভেরিয়েবল থাকে, তবে প্রতিটি ভেরিয়েবলের জন্য আমার কি পৃথক সারির দরকার? দ্বিতীয়ত, উপরোক্ত প্রোগ্রামের সারিগুলি কেন সিঙ্ক হয়? প্রত্যেকের প্রতিটি কার্যক্রমে স্থানীয় অনুলিপি হিসাবে কাজ করা উচিত নয়?

এটি পুরানো, তবে আমি যাই হোক না কেন উত্তর দিন। কিউ নিজেই সিঙ্ক্রোনাইজড নয়, ভেরিয়েবলের চেয়ে বেশি কিছু নয় a। এটি কাতারের ডিফল্ট অবরুদ্ধ আচরণ যা সিঙ্ক্রোনাইজেশন তৈরি করে। a = q.get()একটি মান উপলব্ধ না হওয়া পর্যন্ত বিবৃতিটি অবরুদ্ধ হবে (অপেক্ষা করুন)। ভেরিয়েবল qস্থানীয়: আপনি যদি এটির জন্য আলাদা মান নির্ধারণ করেন তবে এটি কেবল স্থানীয়ভাবে ঘটবে। কোডটিতে এটি নির্ধারিত সারিটি মূল থ্রেলে সংজ্ঞায়িত।

4
থ্রেডগুলির মধ্যে তথ্য তথ্য ভাগ করতে সর্বদা একটি সারি ব্যবহার করা প্রয়োজন হয় না। চিপনার উত্তরের উদাহরণটি পুরোপুরি ঠিক আছে। এছাড়াও, একটি সারি সর্বদা সঠিক সরঞ্জাম নয়। একটি সারি দরকারী, উদাহরণস্বরূপ, যদি আপনি মান উপলব্ধ না হওয়া অবরুদ্ধ করতে চান। যদি দুটি থ্রেড ভাগ করা সংস্থার উপর প্রতিযোগিতা করে তবে এটি অকেজো। অবশেষে, গ্লোবাল ভেরিয়েবলগুলি থ্রেডগুলির মধ্যে সবচেয়ে খারাপ নয়। এগুলি প্রকৃতপক্ষে আরও প্রাকৃতিক হতে পারে। উদাহরণস্বরূপ, আপনার থ্রেডটি কেবল কোডের একটি ব্লক হতে পারে, একটি লুপ বলুন, যার নিজস্ব প্রক্রিয়া প্রয়োজন। আপনি যখন কোনও ফাংশনে লুপটি রাখেন তখন স্থানীয় সুযোগটি কৃত্রিমভাবে তৈরি হয়।

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 এ অনুপ্রবেশ করা সম্ভব হয়, অ-অ্যাক্সেসযোগ্য কোড পৌঁছানোর অনুমতি দেয়। আমি পূর্ববর্তী পোস্টগুলি থেকে একটি সম্পূর্ণ বিক্ষোভ প্রোগ্রামে নীচে ধারণাগুলি সংগঠিত করেছি (নীচে) যে আমি পাইথন ২.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 এ একটি লক অর্জন / রিলিজ পেয়ারটি sertedোকাতাম তখন আমি দেখতে পেলাম যে "অ্যাক্সেসযোগ্য" বার্তা মুদ্রণের সম্ভাবনা অনেক হ্রাস পেয়েছে। বার্তাটি দেখতে আমি ঘুমের সময়টি 0.01 সেকেন্ডে কমিয়ে এনএনকে 1000 এ বাড়িয়েছি।

থ্রেড 1 এ একটি লক অর্জন / রিলিজের জোড় দিয়ে আমি বার্তাটি দেখার আশা করি না, তবে এটি রয়েছে। আমি একটি লক অর্জন / রিলিজ পেয়ারটি থ্রেড 2 এ Afterোকানোর পরে, বার্তাটি আর উপস্থিত হবে না। পূর্ববর্তী সময়ে, থ্রেড 2-তে বর্ধিত বিবৃতি সম্ভবত অ-পরমাণুও।


4
আপনার উভয় থ্রেডে লক প্রয়োজন কারণ এগুলি সমবায়, "পরামর্শমূলক লক" ("বাধ্যতামূলক" নয়)। আপনি ঠিক বলেছেন যে ইনক্রিমেন্ট স্টেটমেন্ট অ-পারমাণবিক।
ডারকনাট

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 পায় But তবে এটি থেমে যায় এবং পরবর্তী থ্রেডে ঝাঁপ দেয় এখানে, T + 1 এ, এর a+1পুরানো মান সহ কার্যকর করে a == 4। সুতরাং এটি 5 টি গণনা করুন (টি + 2 এ) ফিরে যান, থ্রেড 1 এবং a=104মেমরিতে লিখুন । এখন থ্রেড 2 এ ফিরে সময় টি + 3 এবং a=5মেমরিতে লিখুন । ভয়েলা! পরবর্তী মুদ্রণ নির্দেশাবলী 104 এর পরিবর্তে 5 মুদ্রণ করবে।

খুব খারাপ বাজে বাগটি পুনরুত্পাদন এবং ধরা হবে।


একটি সঠিক বাস্তবায়ন যোগ করুন দয়া করে বিবেচনা করুন। থ্রেডগুলির মধ্যে ডেটা ভাগ করা শিখার পক্ষে এটি খুব সহায়ক হবে।
জেএস।

4
"টুডো" তালিকায় যুক্ত হয়েছে :)
ভিজোফট
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.