آموزش پایتون - ارتباط بین‌نخی

ارتباط بین‌نخی (Inter-Thread Communication) به فرآیندی اشاره دارد که امکان ارتباط و همگام‌سازی بین نخ‌ها (Threads) را در یک برنامه چندنخی پایتون فراهم می‌سازد.

به طور کلی، نخ‌ها در پایتون فضای حافظه مشترکی را در یک فرآیند به اشتراک می‌گذارند، که به آن‌ها اجازه می‌دهد داده‌ها را مبادله کرده و فعالیت‌های خود را از طریق متغیرها، اشیا و مکانیزم‌های همگام‌سازی تخصصی ارائه شده توسط ماژول threading هماهنگ کنند.

برای تسهیل ارتباط بین‌نخی، ماژول threading عناصر پایه همگام‌سازی مختلفی مانند اشیای Lock، Event، Condition و Semaphore را ارائه می‌دهد. در این آموزش یاد خواهید گرفت که چگونه از اشیای Event و Condition برای برقراری ارتباط بین نخ‌ها در یک برنامه چندنخی استفاده کنید.

شیء Event

یک شیء Event وضعیت یک پرچم (Flag) داخلی را مدیریت می‌کند تا نخ‌ها بتوانند منتظر بمانند یا آن را تنظیم کنند. شیء Event متدهایی را برای کنترل وضعیت این پرچم ارائه می‌دهد و به نخ‌ها اجازه می‌دهد فعالیت‌های خود را بر اساس شرایط مشترک همگام‌سازی کنند.

پرچم در ابتدا false است و با متد set() برابر با true می‌شود و با متد clear() دوباره به false تغییر می‌یابد. متد wait() تا زمانی که پرچم true شود مسدود می‌ماند.

متدهای کلیدی شیء Event عبارتند از −

  • is_set(): تنها و تنها در صورتی True برمی‌گرداند که پرچم داخلی true باشد.
  • set(): پرچم داخلی را روی true تنظیم می‌کند. تمام نخ‌هایی که منتظر true شدن آن هستند بیدار می‌شوند. نخ‌هایی که پس از true شدن پرچم متد wait() را فراخوانی کنند، اصلاً مسدود نخواهند شد.
  • clear(): پرچم داخلی را روی false بازنشانی می‌کند. متعاقباً، نخ‌هایی که wait() را فراخوانی می‌کنند مسدود می‌شوند تا زمانی که set() برای true کردن مجدد پرچم داخلی فراخوانی شود.
  • wait(timeout=None): تا زمانی که پرچم داخلی true شود مسدود می‌ماند. اگر پرچم داخلی در زمان ورود true باشد، بلافاصله برمی‌گردد. در غیر این صورت، مسدود می‌ماند تا زمانی که نخ دیگری set() را برای true کردن پرچم فراخوانی کند، یا زمان اختیاری timeout به پایان برسد. هنگامی که آرگومان timeout وجود داشته باشد و None نباشد، باید یک عدد ممیز شناور باشد که زمان انتظار را به ثانیه مشخص می‌کند.

مثال

کد زیر تلاش می‌کند جریان ترافیک را که توسط وضعیت چراغ راهنمایی (سبز یا قرمز) کنترل می‌شود، شبیه‌سازی کند.

در این برنامه دو نخ وجود دارد که دو تابع مختلف را هدف قرار می‌دهند. تابع signal_state() به صورت دوره‌ای رویداد را تنظیم و بازنشانی می‌کند که نشان‌دهنده تغییر چراغ از سبز به قرمز است.

تابع traffic_flow() منتظر می‌ماند تا رویداد تنظیم شود و تا زمانی که تنظیم باقی بماند، یک حلقه را اجرا می‌کند.

from threading import Event, Thread
import time

terminate = False

def signal_state():
    global terminate
    while not terminate:
        time.sleep(0.5)
        print("Traffic Police Giving GREEN Signal")
        event.set()
        time.sleep(1)
        print("Traffic Police Giving RED Signal")
        event.clear()

def traffic_flow():
    global terminate
    num = 0
    while num < 10 and not terminate:
        print("Waiting for GREEN Signal")
        event.wait()
        print("GREEN Signal ... Traffic can move")
        while event.is_set() and not terminate:
            num += 1
            print("Vehicle No:", num," Crossing the Signal")
            time.sleep(1)
        print("RED Signal ... Traffic has to wait")

event = Event()
t1 = Thread(target=signal_state)
t2 = Thread(target=traffic_flow)
t1.start()
t2.start()

# Terminate the threads after some time
time.sleep(5)
terminate = True

# join all threads to complete
t1.join()
t2.join()

print("Exiting Main Thread")

خروجی

با اجرای کد بالا خروجی زیر را دریافت خواهید کرد −

Waiting for GREEN Signal
Traffic Police Giving GREEN Signal
GREEN Signal ... Traffic can move
Vehicle No: 1  Crossing the Signal
Traffic Police Giving RED Signal
RED Signal ... Traffic has to wait
Waiting for GREEN Signal
Traffic Police Giving GREEN Signal
GREEN Signal ... Traffic can move
Vehicle No: 2  Crossing the Signal
Vehicle No: 3  Crossing the Signal
Traffic Police Giving RED Signal
Traffic Police Giving GREEN Signal
Vehicle No: 4  Crossing the Signal
Traffic Police Giving RED Signal
RED Signal ... Traffic has to wait
Traffic Police Giving GREEN Signal
Traffic Police Giving RED Signal
Exiting Main Thread

شیء Condition

شیء Condition در ماژول threading پایتون، مکانیزم همگام‌سازی پیشرفته‌تری را ارائه می‌دهد. این شیء به نخ‌ها اجازه می‌دهد قبل از ادامه کار، منتظر اعلان (Notification) از نخ دیگری باشند. اشیای Condition همیشه با یک قفل (Lock) همراه هستند و مکانیزم‌هایی را برای سیگنال‌دهی بین نخ‌ها فراهم می‌کنند.

سینتکس کلاس threading.Condition() به صورت زیر است −

threading.Condition(lock=None)

در ادامه متدهای کلیدی شیء Condition آمده است −

  • acquire(*args): قفل زیرین را در اختیار می‌گیرد. این متد، متد مربوطه را روی قفل زیرین فراخوانی می‌کند؛ مقدار بازگشتی همان چیزی است که آن متد برمی‌گرداند.
  • release(): قفل زیرین را آزاد می‌کند. این متد، متد مربوطه را روی قفل زیرین فراخوانی می‌کند؛ مقدار بازگشتی ندارد.
  • wait(timeout=None): این متد قفل زیرین را آزاد می‌کند و سپس مسدود می‌شود تا زمانی که توسط فراخوانی notify() یا notify_all() برای همان متغیر Condition در نخ دیگری بیدار شود، یا زمان اختیاری timeout به پایان برسد. پس از بیدار شدن یا اتمام زمان، دوباره قفل را به دست می‌آورد و برمی‌گردد.
  • wait_for(predicate, timeout=None): این متد کاربردی ممکن است wait() را به طور مکرر فراخوانی کند تا زمانی که شرط (predicate) برآورده شود یا زمان انتظار به پایان برسد. مقدار بازگشتی، آخرین مقدار بازگشتی شرط خواهد بود و در صورت اتمام زمان انتظار، مقدار False خواهد داشت.
  • notify(n=1): این متد حداکثر n نخ از نخ‌های منتظر برای متغیر Condition را بیدار می‌کند؛ اگر هیچ نخی منتظر نباشد، هیچ عملیاتی انجام نمی‌دهد.
  • notify_all(): تمام نخ‌های منتظر روی این Condition را بیدار می‌کند. این متد مانند notify() عمل می‌کند، اما به جای یک نخ، همه نخ‌های منتظر را بیدار می‌کند. اگر نخ فراخواننده در زمان فراخوانی این متد قفل را به دست نیاورده باشد، یک RuntimeError رخ می‌دهد.

مثال

این مثال شکل ساده‌ای از ارتباط بین‌نخی را با استفاده از شیء Condition در ماژول threading پایتون نشان می‌دهد. در اینجا thread_a و thread_b با استفاده از یک شیء Condition با هم ارتباط برقرار می‌کنند؛ thread_a منتظر می‌ماند تا اعلانی از thread_b دریافت کند. نخ thread_b به مدت ۲ ثانیه می‌خوابد و سپس به thread_a اعلان می‌فرستد و کار خود را به پایان می‌رساند.

from threading import Condition, Thread
import time

c = Condition()

def thread_a():
    print("Thread A started")
    with c:
        print("Thread A waiting for permission...")
        c.wait()
        print("Thread A got permission!")
    print("Thread A finished")

def thread_b():
    print("Thread B started")
    with c:
        time.sleep(2)
        print("Notifying Thread A...")
        c.notify()
    print("Thread B finished")

Thread(target=thread_a).start()
Thread(target=thread_b).start()

خروجی

با اجرای کد بالا خروجی زیر را دریافت خواهید کرد −

Thread A started
Thread A waiting for permission...
Thread B started
Notifying Thread A...
Thread B finished
Thread A got permission!
Thread A finished

مثال

در اینجا کد دیگری وجود دارد که نحوه استفاده از شیء Condition را برای برقراری ارتباط بین نخ‌ها نشان می‌دهد. در این کد، نخ t2 تابع taskB() را اجرا می‌کند و نخ t1 تابع taskA() را اجرا می‌کند. نخ t1 شرط (Condition) را در اختیار گرفته و اعلان صادر می‌کند.

تا آن زمان، نخ t2 در وضعیت انتظار قرار دارد. پس از آزاد شدن condition، نخ منتظر شروع به مصرف عدد تصادفی تولید شده توسط تابع اعلان‌کننده می‌کند.

from threading import Condition, Thread
import time
import random

numbers = []

def taskA(c):
    for _ in range(5):
        with c:
            num = random.randint(1, 10)
            print("Generated random number:", num)
            numbers.append(num)
            print("Notification issued")
            c.notify()
        time.sleep(0.3)

def taskB(c):
    for i in range(5):
        with c:
            print("waiting for update")
            while not numbers: 
                c.wait()
            print("Obtained random number", numbers.pop())
        time.sleep(0.3)

c = Condition()
t1 = Thread(target=taskB, args=(c,))
t2 = Thread(target=taskA, args=(c,))
t1.start()
t2.start()
t1.join()
t2.join()
print("Done")

وقتی این کد را اجرا می‌کنید، خروجی زیر را تولید خواهد کرد −

waiting for update
Generated random number: 2
Notification issued
Obtained random number 2
Generated random number: 5
Notification issued
waiting for update
Obtained random number 5
Generated random number: 1
Notification issued
waiting for update
Obtained random number 1
Generated random number: 9
Notification issued
waiting for update
Obtained random number 9
Generated random number: 2
Notification issued
waiting for update
Obtained random number 2
Done

پیشرفت مطالعه: وضعیت خوانده‌شدن این درس فقط در همین مرورگر ذخیره می‌شود؛ بدون نیاز به حساب کاربری.