وقتی برنامه‌ مون کند میشه یا میخوایم چند تا کار رو هم‌ زمان انجام بدیم، معمولاً دو تا راهکار میاد تو ذهنمون: Threading و Multiprocessing. بیا بدون کلمات قلمبه ‌سلمبه و خیلی خودمونی ببینیم داستان این دو تا چیه، کِی باید از کدوم استفاده کنیم و فرقشون دقیقاً کجاست.

مشکل کجاست؟ (معرفی غولی به نام GIL)

قبل از اینکه بریم سراغ راه‌ حل‌ ها، باید یه واقعیتی رو درباره پایتون (نسخه استاندارد یعنی CPython) بدونی. پایتون یه نگهبان سخت‌گیر داره به اسم GIL (Global Interpreter Lock). کار این نگهبان چیه؟ فقط اجازه میده در هر لحظه، فقط یک دستور پایتوناجرا بشه! یعنی حتی اگر کامپیوترت ۱۶ تا هسته پردازشی داشته باشه، پایتون تو حالت عادی فقط از یکیش استفاده می‌کنه. حالا با این محدودیت، چطور کارهامون رو سریع‌تر کنیم؟ اینجاست که دو تا قهرمان ما وارد میشن!

۱. تردینگ (Threading): کارگر های هم‌اتاقی!

فرض کن یه اتاق داری با چند تا کارگر. همه این کارگر ها به یه میز کار (حافظه یا Memory) دسترسی دارن. در Threading، ما چند تا "ترد" (Thread) می‌سازیم که همگی داخل یک پروسه (Process) اجرا میشن و حافظه مشترک دارن.  خوراکِ چه کارهاییه؟ (I/O-Bound) تردینگ برای کار هایی عالیه که پردازنده (CPU) توشون درگیر نیست، بلکه برنامه مجبوره صبر کنه. مثلاً:

  • دانلود کردن فایل از اینترنت 
  • خوندن و نوشتن روی هارد دیسک 
  • درخواست زدن به دیتابیس یا API  وقتی یه ترد منتظر جواب اینترنته، GIL رو ول می‌کنه و اجازه میده یه ترد دیگه کارش رو بکنه. به این میگن معجزه Threading!  یه مثال خیلی ساده:
import threading
import time
def download_file(name):
    print(f"شروع دانلود فایل {name}...")
    time.sleep(2)  # شبیه‌سازی زمان دانلود
    print(f"دانلود {name} تموم شد! 🎉")
# ساختن تردها
thread1 = threading.Thread(target=download_file, args=("A",))
thread2 = threading.Thread(target=download_file, args=("B",))
# شروع کار
thread1.start()
thread2.start()
# صبر می‌کنیم تا کارشون تموم بشه
thread1.join()
thread2.join()
print("همه فایل‌ها دانلود شدن!")

۲. مولتی‌پروسسینگ (Multiprocessing): کارخونه‌ های مستقل! 

حالا فرض کن به جای یه اتاق، چند تا کارخونه کاملاً مجزا میسازیم! هر کارخونه کارگر ها، میز کار و ابزار خودش رو داره. در Multiprocessing، پایتون واقعاً چند تا پروسه جداگانه توی سیستم‌عامل میسازه. چون هر پروسه پایتونِ خودش و حافظه خودش رو داره، دیگه خبری از محدودیت GIL نیست! هر پروسه روی یه هسته جداگانه از CPU اجرا میشه.  خوراکِ چه کارهاییه؟ (CPU-Bound) مولتی‌پروسسینگ برای کارهایی که پردازنده رو به شدت درگیر میکنن و محاسبات سنگین دارن بی ‌نظیره. مثلاً:

  • پردازش تصویر یا ویدیو 
  • محاسبات سنگین ریاضی و هوش مصنوعی 
  • تحلیل داده‌های خیلی بزرگ (Big Data) یه مثال خیلی ساده:
import multiprocessing
def heavy_math(number):
    print(f"شروع محاسبه سنگین برای {number}...")
    result = sum(i * i for i in range(10_000_000)) # یه محاسبه الکی ولی سنگین
    print(f"محاسبه {number} تموم شد!")
if __name__ == '__main__':
    # ساختن پروسه‌ها
    process1 = multiprocessing.Process(target=heavy_math, args=(1,))
    process2 = multiprocessing.Process(target=heavy_math, args=(2,))
# شروع کار
    process1.start()
    process2.start()
# صبر می‌کنیم تا تموم بشن
    process1.join()
    process2.join()
    print("همه محاسبات انجام شد!")

مقایسه در یک نگاه

برای اینکه قضیه برات کاملاً جا بیفته، بیا این دو تا رو تو یه جدول با هم مقایسه کنیم:

ویژگی Threading (تردینگ) Multiprocessing (مولتی‌پروسسینگ)
فضای حافظه (Memory) مشترک (همه از یه حافظه استفاده می‌کنن) کاملاً جداگانه (هر پروسه حافظه خودش رو داره)
محدودیت GIL درگیرش هست (فقط یه ترد هم‌زمان اجرا میشه) دورش می‌زنه! (اجرای واقعی و هم‌زمان روی چند هسته)
سرعت ایجاد شدن خیلی سریع و سبک کندتر و سنگین‌تر (چون باید پروسه جدید ساخته بشه)
مصرف منابع سیستم کم زیاد (رم بیشتری مصرف می‌کنه)
بهترین کاربرد کارهای شبکه‌ای و منتظر موندن (I/O-Bound) محاسبات سنگین و درگیر کردن CPU (CPU-Bound)
اشتراک‌گذاری اطلاعات خیلی راحت (چون حافظه مشترکه) سخت‌تر (نیاز به ابزارهایی مثل Queue یا Pipe داره)

جمع‌بندی

اگه بخوام کل این مقاله رو تو دو خط برات خلاصه کنم، قانونش اینه:

  1. برنامه‌ت قراره زیاد منتظر بمونه؟ (مثل دانلود، خوندن فایل، کار با دیتابیس)  برو سراغ Threading.
  2. برنامه‌ت قراره مغز کامپیوتر (CPU) رو داغ کنه؟ (مثل ریاضیات، پردازش تصویر، رمزنگاری)برو سراغ Multiprocessing.