مقدمه
در این دوره با سه ابزار اصلی همزمانی در پایتون آشنا شدیم: threading، multiprocessing و asyncio. هر کدام ابزارها و الگوهای خاص خودشان را دارند (Lock، Queue، Semaphore، Event و…) اما سؤالی که همیشه باقی میماند این است: «برای پروژهٔ من، کدام یک مناسبتر است؟» در این درس یک راهنمای عملی برای پاسخ به این سؤال ارائه میدهیم.
قدم اول: نوع کار خود را شناسایی کنید
قبل از انتخاب ابزار، باید بدانیم کار ما در کدام دسته قرار میگیرد:
- I/O-bound: کاری که بیشتر زمانش صرف «انتظار» میشود – مثل درخواست شبکه، خواندن/نوشتن فایل، یا پرسوجوی پایگاه داده. پردازنده در این مدت تقریباً بیکار است.
- CPU-bound: کاری که بیشتر زمانش صرف «محاسبه» میشود – مثل پردازش تصویر، محاسبات ریاضی سنگین، یا فشردهسازی داده. پردازنده در این مدت دائماً مشغول است.
مقایسهٔ سه ابزار
- threading: چند Thread در یک پردازش مشترک. به دلیل GIL، نمیتواند کارهای CPU-bound را واقعاً موازی اجرا کند، اما برای I/O-bound عالی است چون در زمان انتظار I/O، GIL آزاد میشود. سربار کمتری نسبت به Process دارد و حافظه به اشتراک گذاشته میشود (نیاز به Lock برای جلوگیری از Race Condition).
- multiprocessing: چند Process کاملاً مجزا، هر کدام با حافظه و GIL مستقل خودشان. برای کارهای CPU-bound مناسب است چون میتواند از چند هسته پردازنده به صورت واقعی موازی استفاده کند. سربار بیشتری دارد (ساخت Process گرانتر از Thread است) و اشتراک داده نیاز به ابزارهایی مثل Queue یا Manager دارد.
- asyncio: یک Thread با یک حلقهٔ رویداد که در نقاط await بین کوروتینها سوییچ میکند. برای تعداد بسیار زیاد کارهای I/O-bound (مثل هزاران اتصال شبکهٔ همزمان) بسیار کمسربارتر از Thread یا Process است، اما نیاز دارد که کتابخانههای مورد استفاده نسخهٔ async داشته باشند یا با run_in_executor به Thread/Process سپرده شوند.
راهنمای گامبهگام انتخاب
- اگر کار شما I/O-bound است و تعداد عملیات همزمان زیاد است (مثلاً صدها یا هزاران درخواست شبکه) و کتابخانههای async-native (مثل aiohttp یا asyncpg) در دسترساند: asyncio بهترین انتخاب است.
- اگر کار شما I/O-bound است اما با کتابخانههای قدیمی یا Blocking سروکار دارید که نسخهٔ async ندارند: threading (یا run_in_executor در دل asyncio) گزینهٔ مناسبی است.
- اگر کار شما CPU-bound است – یعنی واقعاً به محاسبهٔ موازی روی چند هسته نیاز دارید: multiprocessing (یا ProcessPoolExecutor) را انتخاب کنید.
- اگر پروژهٔ شما ترکیبی از هر دو نوع کار است (مثلاً یک سرور وب که هم باید به درخواستهای شبکه پاسخ دهد و هم محاسبات سنگین انجام دهد): از asyncio به عنوان هسته اصلی استفاده کنید و کارهای Blocking یا CPU-bound را با run_in_executor به ThreadPoolExecutor یا ProcessPoolExecutor بسپارید.
چند مثال واقعی
- وباسکرپر با هزاران درخواست همزمان: asyncio + aiohttp
- اسکریپتی که چند فایل بزرگ را دانلود میکند (با کتابخانهٔ requests که async نیست): threading
- پردازش دستهای تصاویر یا ویدیو روی چند هسته: multiprocessing
- سرور وب async (مثل FastAPI) که گاهی باید یک تابع محاسباتی سنگین صدا بزند: asyncio به عنوان هسته + run_in_executor برای آن بخش سنگین
جمعبندی
نکتهٔ کلیدی این است که این سه ابزار رقیب هم نیستند، بلکه مکمل هماند: threading و multiprocessing را میتوان حتی درون یک برنامهٔ asyncio هم استفاده کرد (از طریق run_in_executor). انتخاب درست همیشه با شناسایی نوع کار – I/O-bound یا CPU-bound – شروع میشود. در درس پایانی دوره، یک جمعبندی کلی از تمام مفاهیمی که در این دوره آموختیم ارائه میدهیم.