پایتون ۳.۱۴ و اجرای واقعاً موازی؛ راهنمای ارزیابی Free-Threaded CPython
برای سالها، اجرای همزمان چند نخ در پایتون بهدلیل وجود قفل سراسری مفسر یا GIL نمیتوانست از تمام هستههای پردازنده برای اجرای بایتکد پایتون استفاده کند. Free-Threaded CPython این فرض معماری را تغییر میدهد؛ اما استفاده از آن صرفاً با نصب یک نسخه جدید از پایتون آغاز نمیشود. تیم باید آن را یک هدف اجرایی مستقل بداند و سازگاری وابستگیها، مالکیت داده، همگامسازی، مصرف حافظه و رفتار سرویس زیر بار واقعی را جداگانه ارزیابی کند.
چرا این موضوع مهم است؟
بخش مهمی از سرویسهای مدرن پایتونی، بهویژه سامانههای هوش مصنوعی، پردازش اسناد و تحلیل داده، بخشی از زمان خود را صرف محاسبات CPU-bound میکنند. در این workloads، نخهای معمولی بهدلیل GIL به موازیسازی کامل نمیرسند و استفاده از چند پردازش نیز با هزینههایی مانند مصرف حافظه بیشتر، پیچیدگی ارتباط بین پردازشها و دشواری اشتراک داده همراه است.
نسخه بدون GIL میتواند بخشی از این فاصله را کاهش دهد و در برخی سرویسها معماری سادهتری نسبت به multiprocessing ایجاد کند. بااینحال، افزایش throughput تنها معیار تصمیمگیری نیست. اگر p95 latency، مصرف RAM، نرخ خطا یا هزینه اجرای هر درخواست افزایش پیدا کند، بهبود خام پردازنده الزاماً به نتیجه بهتر در محیط production منجر نمیشود. بنابراین مسئله اصلی، حذف GIL نیست؛ بلکه انتخاب runtime مناسب بر اساس اندازهگیری دقیق workload واقعی است.
مسئلهای که حل میکند
Free-Threaded CPython اجازه میدهد چند نخ، بایتکد پایتون را بهصورت همزمان روی هستههای مختلف پردازنده اجرا کنند. این ویژگی برای کارهایی مانند قطعهبندی متن، تولید ویژگیهای اولیه، پردازش دادههای ساختیافته و برخی مراحل پیشپردازش مدلهای هوش مصنوعی جذاب است؛ بهخصوص زمانی که گلوگاه اصلی، محاسبات پایتونی باشد نه انتظار برای شبکه یا دیسک.
برای نمونه، یک سرویس FastAPI را در نظر بگیرید که سند را دریافت میکند، متن آن را استخراج و قطعهبندی میکند و سپس embedding یا ویژگیهای اولیه میسازد. درخواستهای شبکه و عملیات ذخیرهسازی میتوانند همچنان با asyncio مدیریت شوند، اما مرحله محاسباتی را میتوان میان چند نخ توزیع کرد. در نسخه Free-Threaded، این نخها امکان استفاده واقعی از چند هسته را دارند.
بااینحال، این سناریو فقط زمانی موفق است که tokenizer، کتابخانههای عددی و افزونههای نوشتهشده با زبان C بررسی شده باشند. یک وابستگی ناسازگار ممکن است دوباره GIL را فعال کند، رفتار نخها را محدود سازد یا در صورت وجود state مشترک، race condition ایجاد کند. به همین دلیل، آزمایش یک نمونه کوچک و قابلاندازهگیری باید پیش از هر تصمیم معماری انجام شود.
راهنمای عملی برای استفاده در پروژههای واقعی
بهتر است مهاجرت را بهعنوان یک آزمایش مهندسی کنترلشده ببینید، نه تغییر سراسری نسخه پایتون. ابتدا سرویسهایی را پیدا کنید که سهم CPU در آنها بالاست و سپس نسخه فعلی runtime، multiprocessing و نسخه Free-Threaded را با workload یکسان مقایسه کنید.
چکلیست پیشنهادی پیادهسازی
- نقاط CPU-bound، contention و صفهای پردازشی را با دادههای واقعی شناسایی کنید.
- نسخه Free-Threaded و تمام وابستگیهای اصلی را در یک محیط جداگانه نصب و ثبت کنید.
- سازگاری wheelهای باینری، افزونههای C، tokenizerها و کتابخانههای عددی را بررسی کنید.
- برای baseline، throughput، تأخیر p95، مصرف CPU، مصرف RAM و نرخ خطا را ثبت کنید.
- state مشترک، cacheهای قابلتغییر، iteratorها و اشیای دارای چرخه عمر مشترک را فهرست کنید.
- مرز مالکیت داده را مشخص کنید و ارتباط میان نخها را با صفهای thread-safe و پیامهای تغییرناپذیر انجام دهید.
- هر نخ را با event loop مستقل یا مدل ارتباطی مشخص اجرا کنید و از اشتراکگذاری مستقیم task و future میان نخها پرهیز کنید.
- یک canary deployment محدود با شاخصهای قابل بازگشت و مسیر rollback آماده کنید.
- نتیجه را با runtime فعلی و multiprocessing مقایسه کنید؛ صرفاً با افزایش مصرف CPU تصمیم نگیرید.
یک الگوی مناسب برای ارزیابی
آزمایش باید سه نوع بار را پوشش دهد: بار محاسباتی خالص، بار ترکیبی محاسبات و ورودیوخروجی، و الگوی نزدیک به ترافیک واقعی. برای هر حالت، علاوه بر میانگین، توزیع تأخیر، رفتار در زمان افزایش همزمانی، مصرف حافظه و نرخ خطا را اندازهگیری کنید. اگر بهبود فقط در بار مصنوعی دیده میشود اما در ترافیک واقعی از بین میرود، مهاجرت ارزش عملیاتی ندارد.
اشتباهات رایج
- فرض کردن اینکه حذف GIL همه برنامهها را سریعتر میکند؛ سرویسهای صرفاً I/O-bound ممکن است سود محدودی ببرند.
- نادیده گرفتن ناسازگاری افزونههای C و wheelهای باینری یا فرض کردن اینکه همه کتابخانهها بهصورت خودکار thread-safe هستند.
- اعتماد به رفتار ضمنی dict، list یا set بهجای طراحی صریح برای همگامسازی.
- اشتراکگذاری event loop، task یا future میان نخهایی که چرخه اجرای مستقل دارند.
- قفلگذاری گسترده روی کل مسیر پردازش و از بین بردن مزیت موازیسازی.
- مقایسه نکردن مصرف حافظه، هزینه زیرساخت و تراکم containerها زیر بار production.
- فعالسازی سراسری نسخه جدید پیش از اجرای canary و تهیه baseline قابل اتکا.
نکات معماری، کارایی و امنیت
- برای عملیات I/O از asyncio و برای محاسبات CPU-bound از تعداد کنترلشدهای نخ استفاده کنید؛ افزایش بیهدف تعداد نخها میتواند contention و مصرف حافظه را بیشتر کند.
- هر نخ باید event loop مستقل یا قرارداد مشخصی برای تعامل با event loop داشته باشد. مرز مالکیت داده را مستند کنید.
- پیامهای میان نخها را تا حد امکان immutable نگه دارید و برای صفها از سازوکارهای thread-safe استفاده کنید.
- runtime بدون GIL را بهعنوان یک target جداگانه در CI آزمایش کنید و تستهای همزمانی را در کنار تستهای معمول قرار دهید.
- race condition ممکن است به خرابشدن state، پردازش ناقص یا حتی نشت داده میان درخواستها منجر شود؛ پس لاگگذاری و پایش باید شناسه درخواست و مالکیت داده را قابل ردیابی کند.
- افزایش مصرف RAM میتواند تراکم containerها را کاهش دهد و هزینه زیرساخت را بالا ببرد؛ ظرفیتسنجی را با محدودیتهای واقعی محیط اجرا انجام دهید.
- برای وابستگیهای باینری، نسخه سازگار، فرایند بهروزرسانی و امکان بازگشت به build معمولی را مشخص کنید.
- در تصمیم نهایی، throughput، تأخیر p95، نرخ خطا، مصرف CPU و RAM و هزینه هر درخواست را همزمان بررسی کنید.
برداشت من
برداشت من از این مطلب این است که...
Free-Threaded Python بیشتر یک فرصت معماری است تا گزینهای ساده برای تغییر نسخه runtime. تیمها نباید فقط با دیدن امکان اجرای موازی، همه سرویسها را به این build منتقل کنند. ابتدا باید workloadهای CPU-bound شناسایی شوند، وابستگیهای باینری و رفتار state مشترک بررسی شوند و baseline دقیقی از throughput، تأخیر p95، مصرف حافظه و نرخ خطا ثبت شود.
پس از آن، منطقی است یک سرویس محدود با نسخه بدون GIL آزمایش شود و نتیجه آن با multiprocessing و runtime فعلی مقایسه شود. اگر بهبود در هزینه یا عملکرد واقعاً پایدار بود، rollout سرویسبهسرویس و تدریجی از مهاجرت یکباره ایمنتر و قابلکنترلتر است.
منابع رسمی
- https://www.python.org/downloads/release/python-3140/
- https://docs.python.org/3.14/howto/free-threading-python.html
- https://docs.python.org/3.14/library/asyncio-threading.html
- https://peps.python.org/pep-0779/
- https://docs.python.org/3.14/library/threading.html
گام بعدی: یک سرویس CPU-bound را انتخاب کنید، baseline تولید ثبت کنید و همان workload را در یک محیط آزمایشی با CPython بدون GIL اجرا کنید؛ تصمیم مهاجرت را فقط بر اساس مقایسه عددی و قابل تکرار بگیرید.
هنوز دیدگاهی تایید نشده است.