سطح مقدماتی (Beginner Level)
در دنیای برنامهنویسی، گاهی نیاز داریم چندین کار را همزمان انجام دهیم. مثلاً میخواهیم در حالی که یک فایل بزرگ دانلود میشود، کاربر بتواند همچنان با برنامه کار کند. پایتون برای این کار دو مفهوم اصلی دارد: Threading (نخها) و Multiprocessing (پردازش چندگانه).
۱. مفهوم همزمانی (Concurrency) و موازیسازی (Parallelism)
قبل از کدنویسی باید تفاوت این دو را بدانیم:
- Threading (همزمانی): مثل یک آشپز است که همزمان مراقب چند قابلمه غذاست. او بین کارها "سویچ" میکند (Time Slicing). برای کارهای I/O Bound (مثل دانلود فایل، درخواست شبکه) عالی است.
- Multiprocessing (موازیسازی): مثل داشتن چند آشپز در آشپزخانه است. هر کدام مستقل روی یک کار تمرکز دارند. برای کارهای CPU Bound (مثل محاسبات ریاضی سنگین) عالی است.
۲. کار با ماژول threading
سادهترین راه برای اجرای کدها به صورت همزمان، استفاده از کلاس Thread است.
مثال اول: ساخت و اجرای یک Thread ساده
در این مثال، یک تابع را در یک ترد جداگانه اجرا میکنیم تا برنامه اصلی منتظر پایان آن نماند.
مثال دوم: اجرای چندین Thread همزمان
میتوانیم چندین ترد بسازیم که همزمان کار کنند.
۳. کلاس Thread و آرگومانها
برای ارسال ورودی به تابعی که در ترد اجرا میشود، از پارامتر args (به صورت تاپل) یا kwargs (دیکشنری) استفاده میکنیم.
# 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__": باشند.
۲. شرایط مسابقه (Race Conditions) و استفاده از Lock
وقتی چندین ترد سعی میکنند همزمان یک متغیر مشترک را تغییر دهند، دادهها خراب میشوند (Race Condition). برای جلوگیری از این مشکل، از Lock استفاده میکنیم.
مثال حرفهای ۲: مشکل Race Condition و حل آن با Lock
۳. ماژول مدرن concurrent.futures
از پایتون ۳.۲ به بعد، توصیه میشود به جای مدیریت دستی تردها، از ThreadPoolExecutor یا ProcessPoolExecutor استفاده کنید. این روش کد تمیزتر و مدیریت خطای بهتری دارد.
مثال حرفهای ۳: استفاده از ThreadPoolExecutor
این روش به طور خودکار تردها را مدیریت میکند و دریافت خروجی (Return Value) از تردها را بسیار ساده میکند.
۴. مقایسه سریع (Best Practices)
- I/O Bound (شبکه، دیسک): از
threadingیاasyncioاستفاده کنید. سربار (Overhead) کمتری دارند. - CPU Bound (محاسبات ریاضی، پردازش تصویر): حتماً از
multiprocessingاستفاده کنید تا از تمام هستههای CPU بهره ببرید. - Daemon Threads: اگر تردی دارید که باید در پسزمینه اجرا شود و با بسته شدن برنامه اصلی بسته شود،
daemon=Trueرا هنگام ساخت ست کنید.
# Static snippet for Daemon thread
t = threading.Thread(target=background_task, daemon=True)
t.start()
# برنامه اصلی اگر تمام شود، منتظر این ترد نمیماند.