آموزش ماژول multiprocessing – انواع روش‌های ارتباط Process‌ها

Please login to bookmark Close

مقدمه

در برنامه‌نویسی با multiprocessing، هر پردازه (process) فضای حافظه‌ی جداگانه‌ی خودش را دارد؛ بنابراین برخلاف تردها، نمی‌توانند به‌سادگی یک متغیر مشترک را ببینند. برای همین به «ارتباط بین پردازه‌ای» (IPC) نیاز داریم. پایتون چند روش برای این کار فراهم کرده که هرکدام ویژگی‌ها و محدودیت‌های خودش را دارد. در ادامه سه روش اصلی را با مثال بررسی می‌کنیم و در پایان با یک جدول مقایسه می‌کنیم.

۱. استفاده از Queue (صف)

Queue یکی از پرکاربردترین روش‌های ارتباط بین پردازه‌هاست. صفِ ماژول multiprocessing به‌صورت امن (thread/process-safe) عمل می‌کند؛ یعنی می‌توانید بدون نگرانی از همگام‌سازی دستی، از آن برای ارسال و دریافت داده بین پردازه‌ها استفاده کنید. یک پردازه داده را put می‌کند و پردازه‌ی دیگر آن را get:

from multiprocessing import Process, Queue

def producer(queue):
    for i in range(5):
        queue.put(i)
    queue.put(None)          # sentinel: signals end of data

def consumer(queue):
    while True:
        item = queue.get()
        if item is None:
            break
        print(f"Consumed: {item}")

if __name__ == "__main__":
    queue = Queue()

    producer_process = Process(target=producer, args=(queue,))
    consumer_process = Process(target=consumer, args=(queue,))

    producer_process.start()
    consumer_process.start()

    producer_process.join()
    consumer_process.join()

مقدار ویژه‌ی None در انتها، به مصرف‌کننده می‌گوید «دیگر داده‌ای نمی‌آید، حلقه را تمام کن». توجه کنید که کل کد باید داخل if __name__ == "__main__": باشد؛ این در multiprocessing ضروری است.

مثال واقعی: توزیع کار بین چند کارگر

رایج‌ترین کاربرد واقعیِ Queue، تقسیم یک دسته کار سنگین (CPU-bound) بین چند پردازه‌ی کارگر است تا روی چند هسته‌ی پردازنده موازی اجرا شوند. مثلاً پردازش دسته‌ای از تصاویر:

from multiprocessing import Process, Queue

def worker(task_queue, result_queue):
    while True:
        job = task_queue.get()
        if job is None:
            break
        result_queue.put((job, job * job))   # pretend this is heavy CPU work

if __name__ == "__main__":
    task_queue = Queue()
    result_queue = Queue()

    jobs = range(10)
    for j in jobs:
        task_queue.put(j)

    workers = [Process(target=worker, args=(task_queue, result_queue)) for _ in range(3)]
    for w in workers:
        task_queue.put(None)      # one stop signal per worker
    for w in workers:
        w.start()
    for w in workers:
        w.join()

    while not result_queue.empty():
        print(result_queue.get())

۲. استفاده از Pipe (لوله)

Pipe یک کانال دوطرفه بین دو پردازه است. هر دو سر لوله می‌توانند داده بفرستند و بگیرند، اما نسبت به Queue سطح‌پایین‌تر است و باید خودتان جریان داده را مدیریت کنید تا دچار بن‌بست نشوید. در مثال زیر، فرستنده پنج عدد و در پایان یک None (به‌عنوان علامت پایان) می‌فرستد و گیرنده تا رسیدن None می‌خواند:

from multiprocessing import Process, Pipe

def sender(conn):
    for i in range(5):
        conn.send(i)
        print(f"send: {i}")
    conn.send(None)          # sentinel to signal the end
    conn.close()

def receiver(conn):
    while True:
        item = conn.recv()
        if item is None:
            break
        print(f"Received: {item}")
    conn.close()

if __name__ == "__main__":
    sender_conn, receiver_conn = Pipe()

    sender_process = Process(target=sender, args=(sender_conn,))
    receiver_process = Process(target=receiver, args=(receiver_conn,))

    sender_process.start()
    receiver_process.start()

    sender_process.join()
    receiver_process.join()

نکته: استفاده از یک علامت پایان (اینجا None) امن‌تر از تکیه بر conn.poll() است؛ چون poll() فقط می‌گوید «همین حالا داده‌ای هست یا نه» و اگر گیرنده کمی زودتر از فرستنده اجرا شود، ممکن است حلقه پیش از رسیدن داده‌ها تمام شود.

۳. استفاده از متغیرهای مشترک (Value و Array)

گاهی به‌جای فرستادن پیام، می‌خواهیم چند پردازه به یک قطعه‌ی حافظه‌ی مشترک دسترسی داشته باشند. multiprocessing برای این کار Value (یک مقدار تکی) و Array (یک آرایه) را فراهم می‌کند که در حافظه‌ی مشترک قرار می‌گیرند و همه‌ی پردازه‌ها تغییراتشان را می‌بینند:

from multiprocessing import Process, Array, Value

def square_numbers(numbers, result, first_square):
    for i in range(len(numbers)):
        result[i] = numbers[i] ** 2
    first_square.value = numbers[0] ** 2      # set the shared Value

if __name__ == "__main__":
    shared_array = Array('i', range(5))       # 'i' = array of integers
    shared_value = Value('i', 0)              # 'i' = a single integer
    numbers = [1, 2, 3, 4, 5]

    process = Process(target=square_numbers, args=(numbers, shared_array, shared_value))
    process.start()
    process.join()

    print("Shared Array:", list(shared_array))   # [1, 4, 9, 16, 25]
    print("Shared Value:", shared_value.value)   # 1

حرف 'i' نوع داده را مشخص می‌کند (اینجا عدد صحیح). نکته‌ی مهم: اگر چند پردازه هم‌زمان بخواهند یک Value یا Array مشترک را تغییر دهند، دوباره با خطر Race Condition روبه‌رو می‌شویم؛ در آن حالت باید از multiprocessing.Lock برای محافظت استفاده کنید (که در درس جداگانه‌ی قفل در multiprocessing بررسی شده است).

جدول مقایسه

روشتعداد طرفینایمن به‌صورت خودکار؟مناسب برای
Queueچند تولیدکننده/مصرف‌کنندهبله (process-safe)توزیع کار و جریان پیام‌ها
Pipeدقیقاً دو پردازهخیر (مدیریت دستی)ارتباط سریع و ساده‌ی دو طرفه
Value / Arrayچند پردازهخیر (به Lock نیاز دارد)اشتراک مستقیم چند مقدار در حافظه

جمع‌بندی

  • چون پردازه‌ها حافظه‌ی مشترک ندارند، برای تبادل داده به IPC نیاز داریم.
  • Queue: امن و پرکاربرد، عالی برای توزیع کار بین چند کارگر.
  • Pipe: کانال دوطرفه‌ی سریع بین دو پردازه؛ سطح‌پایین‌تر و نیازمند مدیریت دستی.
  • Value/Array: اشتراک مستقیم داده در حافظه؛ برای نوشتنِ هم‌زمان به Lock نیاز دارند.

Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (72%)
نظرات

دیدگاهتان را بنویسید

72%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

این قسمت تمرین ندارد!

پاسخ تمرین ها

هنوز برای تمرین‌های این قسمت پاسخی ثبت نشده است!

اشتراک گذاری

چرا بهتره از فیلترشکن استفاده کنید؟

من همه ویدئو ها و پادکست های کُدباز رو توی یوتیوب و ساندکلود و پلتفرم هایی آپلود می‌کنم که اغلب فیلتر هستند.

اغلب آموزش‌ها ویدئو و پادکست دارند. پس اگر می‌خواهید از محتوای سایت بیشترین استفاده رو ببرید نیاز به فیلتر شکن دارید.

توجه داشته باشید که برای خرید از فروشگاه بهتره فیلتر شکن رو خاموش کنید.

تنظیمات

انتخاب زبان
تغییر تم