خانه / آموزش‌ها / آموزش Threading و Multiprocessing در پایتون

آموزش Threading و Multiprocessing در پایتون

🐍 HomeOfPython
|
📅 1404/10/19

سطح مقدماتی (Beginner Level)

در دنیای برنامه‌نویسی، گاهی نیاز داریم چندین کار را همزمان انجام دهیم. مثلاً می‌خواهیم در حالی که یک فایل بزرگ دانلود می‌شود، کاربر بتواند همچنان با برنامه کار کند. پایتون برای این کار دو مفهوم اصلی دارد: Threading (نخ‌ها) و Multiprocessing (پردازش چندگانه).

۱. مفهوم همزمانی (Concurrency) و موازی‌سازی (Parallelism)

قبل از کدنویسی باید تفاوت این دو را بدانیم:

  • Threading (همزمانی): مثل یک آشپز است که همزمان مراقب چند قابلمه غذاست. او بین کارها "سویچ" می‌کند (Time Slicing). برای کارهای I/O Bound (مثل دانلود فایل، درخواست شبکه) عالی است.
  • Multiprocessing (موازی‌سازی): مثل داشتن چند آشپز در آشپزخانه است. هر کدام مستقل روی یک کار تمرکز دارند. برای کارهای CPU Bound (مثل محاسبات ریاضی سنگین) عالی است.

۲. کار با ماژول threading

ساده‌ترین راه برای اجرای کدها به صورت همزمان، استفاده از کلاس Thread است.

مثال اول: ساخت و اجرای یک Thread ساده

در این مثال، یک تابع را در یک ترد جداگانه اجرا می‌کنیم تا برنامه اصلی منتظر پایان آن نماند.

Python

مثال دوم: اجرای چندین Thread همزمان

می‌توانیم چندین ترد بسازیم که همزمان کار کنند.

Python

۳. کلاس Thread و آرگومان‌ها

برای ارسال ورودی به تابعی که در ترد اجرا می‌شود، از پارامتر args (به صورت تاپل) یا kwargs (دیکشنری) استفاده می‌کنیم.

python
# Static: Function definition for context
def process_user_data(user_id, database_name, verbose=False):
    # کدهای پردازش دیتابیس...
    pass

# نحوه فراخوانی صحیح در ترد:
# t = threading.Thread(target=process_user_data, args=(101, "users_db"), kwargs={"verbose": True})

سطح پیشرفته (Professional Level)

در سطح حرفه‌ای، باید با محدودیت‌های پایتون (GIL)، مدیریت منابع مشترک (Locking) و استفاده از تمام هسته‌های CPU آشنا شویم.

۱. قفل مفسر جهانی (GIL) و multiprocessing

در مفسر استاندارد پایتون (CPython)، یک مکانیزم به نام Global Interpreter Lock (GIL) وجود دارد که اجازه نمی‌دهد بیش از یک ترد همزمان کدهای بایت پایتون را اجرا کند. این یعنی threading روی یک هسته CPU اجرا می‌شود و برای محاسبات سنگین سرعت را افزایش نمی‌دهد. راه حل؟ استفاده از Multiprocessing که برای هر پردازش، یک مفسر جداگانه و حافظه مجزا می‌سازد.

مثال حرفه‌ای ۱: استفاده از multiprocessing برای دور زدن GIL

نکته مهم: در ویندوز و برخی سیستم‌ها، کدهای multiprocessing حتما باید داخل if __name__ == "__main__": باشند.

Python

۲. شرایط مسابقه (Race Conditions) و استفاده از Lock

وقتی چندین ترد سعی می‌کنند همزمان یک متغیر مشترک را تغییر دهند، داده‌ها خراب می‌شوند (Race Condition). برای جلوگیری از این مشکل، از Lock استفاده می‌کنیم.

مثال حرفه‌ای ۲: مشکل Race Condition و حل آن با Lock

Python

۳. ماژول مدرن concurrent.futures

از پایتون ۳.۲ به بعد، توصیه می‌شود به جای مدیریت دستی تردها، از ThreadPoolExecutor یا ProcessPoolExecutor استفاده کنید. این روش کد تمیزتر و مدیریت خطای بهتری دارد.

مثال حرفه‌ای ۳: استفاده از ThreadPoolExecutor

این روش به طور خودکار تردها را مدیریت می‌کند و دریافت خروجی (Return Value) از تردها را بسیار ساده می‌کند.

Python

۴. مقایسه سریع (Best Practices)

  • I/O Bound (شبکه، دیسک): از threading یا asyncio استفاده کنید. سربار (Overhead) کمتری دارند.
  • CPU Bound (محاسبات ریاضی، پردازش تصویر): حتماً از multiprocessing استفاده کنید تا از تمام هسته‌های CPU بهره ببرید.
  • Daemon Threads: اگر تردی دارید که باید در پس‌زمینه اجرا شود و با بسته شدن برنامه اصلی بسته شود، daemon=True را هنگام ساخت ست کنید.
python
# Static snippet for Daemon thread
t = threading.Thread(target=background_task, daemon=True)
t.start()
# برنامه اصلی اگر تمام شود، منتظر این ترد نمی‌ماند.