پایتون یکی از محبوب‌ترین زبان‌های برنامه‌نویسی است، اما یک ویژگی پنهان به نام «GIL» اغلب سرعت اجرای برنامه‌های چند‌رشته‌ای را به‌شدت کاهش می‌دهد. این قفل مفسر که دهه‌ها پیش برای مدیریت حافظه طراحی شد، امروزه به یکی از بحث‌برانگیزترین مباحث در اکوسیستم این زبان تبدیل شده است.

قفل مفسر جهانی چیست و چرا طراحی شد

قفل مفسر جهانی، که مخفف Global Interpreter Lock است، یک مکانیزم قفل‌گذاری (Mutex) در مفسر CPython محسوب می‌شود که تنها به یک نخ اجازه می‌دهد در هر لحظه مشخص، کنترل مفسر را در دست داشته باشد. این تصمیم معماری باعث می‌شود حتی در سیستم‌های چند‌هسته‌ای، برنامه‌های چند‌نخی نتوانند کدهای وابسته به پردازنده را به‌صورت موازیِ واقعی اجرا کنند. برای برنامه‌های تک‌نخی این موضوع نامرئی است، اما در کدهای چند‌نخی به یک گلوگاه عملکردی قابل‌توجه تبدیل می‌شود.

ریشهٔ «GIL» مستقیماً در نظامِ مدیریتِ حافظهٔ پایتون نهفته است. CPython برای مدیریت حافظه از روش شمارش‌مرجع استفاده می‌کند. هر شیء ساخته‌شده دارای یک متغیر شمارشی است که تعداد ارجاعات به آن را ردیابی می‌کند. وقتی این مقدار به صفر می‌رسد، حافظه آزاد می‌شود. در محیطِ چند‌نخی، اگر دو نخ هم‌زمان بخواهند این شمارنده را تغییر دهند، شرایطِ رقابتی (Race conditions) رخ می‌دهد. نتیجهٔ این وضعیت می‌تواند نشتِ حافظه یا آزادسازیِ اشتباهِ منابع و کرشِ برنامه باشد.

توسعه‌دهندگان CPython با دو مسیر روبه‌رو بودند: افزودن قفل به تک‌تکِ اشیاء یا طراحی یک قفلِ واحدِ سراسری. انتخابِ اول خطر بن‌بست (Deadlock) و سربارِ عملکردیِ شدید را به‌همراه داشت. راه‌حل نهایی، قرار دادنِ یک قفلِ واحد روی خودِ مفسر بود. این طرح از بن‌بست جلوگیری می‌کرد، اجرا را برای برنامه‌های تک‌نخی سریع نگه می‌داشت و ادغامِ افزونه‌های C ناایمن در برابر نخ‌ها را آسان می‌کرد. مستندات رسمی پایتون تأیید می‌کنند که این انتخاب، در آن مقطع زمانی، سریع‌ترین مسیر برای عرضهٔ پایتون به بازار بوده است.

نمودار معماری قفل مفسر جهانی در پایتون

تأثیر معماری بر برنامه‌های هم‌زمان

بررسیِ اثرِ این قفل بدون تفکیکِ نوعِ بارِ کاری، گمراه‌کننده خواهد بود. رفتارِ پایتون به‌شدت به ماهیتِ وظیفه‌ای که اجرا می‌کنید وابسته است.

بارهای وابسته به پردازنده

برنامه‌هایی که محاسباتِ ریاضیِ سنگین، پردازشِ تصویر، فشرده‌سازی یا هش‌کردنِ داده‌ها را انجام می‌دهند، به‌شدت تحتِ تأثیرِ قفلِ مفسر قرار می‌گیرند. در این سناریوها، نخ‌ها به‌جای اجرایِ هم‌زمان، به نوبت واردِ مفسر می‌شوند. یک برنامهٔ شمارشِ معکوسِ ساده را در نظر بگیرید که یک مقدار را ۵۰ میلیون بار کاهش می‌دهد. اجرایِ تک‌نخیِ آن روی یک پردازندهٔ ۴ هسته‌ای حدود ۳.۲ ثانیه طول می‌کشد. اگر همین کار را بین دو نخ تقسیم کنید، زمانِ اجرایی تقریباً ثابت باقی می‌ماند. قفل اجازه نمی‌دهد هسته‌های موازی هم‌زمان بایت‌کد اجرا کنند و عملاً چند‌نخی‌سازی در این حالت، تک‌هسته‌ای محض است.

بارهای وابسته به ورودی و خروجی

در مقابل، برنامه‌های وب‌اسکرپینگ، ارتباط با دیتابیس یا دانلود فایل، بخشِ عمدهٔ زمان را منتظرِ پاسخِ شبکه یا دیسک می‌مانند. در این بازه‌های انتظار، نخ‌ها قفلِ مفسر را آزاد می‌کنند تا نخ‌های دیگر کار کنند. نتیجه این است که برنامه‌های وابسته به ورودی و خروجی بدون افتِ عملکردِ قابل‌توجه، می‌توانند از چند‌نخی بهرهٔ کامل ببرند.

راهکارهای کاهش و آیندهٔ معماری پایتون

مهندسیِ نرم‌افزارِ مدرن به دنبال پر کردنِ شکاف بینِ طراحیِ کلاسیک و نیازهایِ پردازشیِ امروز است. توسعه‌دهندگان با سه استراتژیِ اصلی این محدودیت را دور می‌زنند.

  • استفاده از ماژولِ پردازشِ چندگانه (multiprocessing) — این روش به‌جای نخ، فرایندهای مستقل می‌سازد که هر کدام فضایِ حافظه و مفسرِ جداگانه دارند و از تمامِ هسته‌های CPU بهره می‌برند.
  • برنامه‌نویسیِ ناهم‌گام (asyncio) — برای کارهای شبکه‌ای و I/O، رویدادمحوری بدونِ سربارِ مدیریتِ چند‌هسته‌ای، الگویِ بهینه‌ای است که در فریم‌ورک‌های مدرن وب مانند FastAPI پیاده‌سازی شده است.
  • اجرایِ کدهای سطحِ پایین — نگارشِ بخش‌های حساسِ به عملکرد با C یا Cython و خارج‌کردنِ آن‌ها از قفلِ مفسر، در بسیاری از پروژه‌های علمی و داده‌کاوی مؤثر است.

نقطهٔ عطفِ این بحث در پایتونِ ۳.۱۳ رقم خورد. طرحِ PEP ۷۰۳ به‌صورت رسمی حالتِ «Free-threaded» را بدونِ قفلِ مفسر پیاده‌سازی می‌کند. این تغییرِ بزرگ، سیستمِ شمارش‌مرجع را با الگوریتم‌های زباله‌روبِ هم‌زمان (thread-safe) جایگزین می‌کند تا خطرِ شرایطِ رقابتی کاهش یابد. اگرچه این حالت به‌صورت پیش‌فرض فعال نیست و برخی افزونه‌های C قدیمی نیاز به بازنویسی دارند، اما مسیر را برای بهره‌برداریٔ واقعی از معماری‌های چند‌هسته‌ای هموار کرده است.

پایتون در حال گذار از یک معماریِ تک‌هسته‌ایِ کلاسیک به سمتِ نسلِ جدیدی از پردازشِ موازی است. با پیاده‌سازیِ رسمیِ حالتِ Free-threaded و بهبودهای زیرساختی، قفلِ مفسر دیگر به یک سدِ غیرقابلِ عبور تبدیل نخواهد شد. توسعه‌دهندگانی که امروز دینامیکِ این مکانیزم را درک می‌کنند، در آستانهٔ بهره‌برداری از تمامِ ظرفیتِ سخت‌افزارهای چند‌هسته‌ای قرار خواهند گرفت.