چندین مفسر در یک فرایند پایتون¶
هرچند در بیشتر موارد شما تنها یک مفسر پایتون را تعبیه میکنید، مواردی وجود دارد که نیاز دارید چندین مفسر مستقل را در همان فرایند و شاید حتی در همان نخ ایجاد کنید. زیرمفسرها به شما امکان انجام این کار را میدهند.
مفسر «اصلی» نخستین مفسری است که هنگام راهاندازی رانتایم ایجاد میشود. این مفسر معمولاً تنها مفسر پایتون در یک فرایند است. برخلاف زیرمفسرها، مفسر اصلی مسئولیتهای منحصربهفردی در سطح کل فرایند مانند مدیریت سیگنال را بر عهده دارد. این مفسر همچنین مسئول اجرا در طول راهاندازی رانتایم است و معمولاً مفسر فعال در طول نهاییسازی رانتایم است. تابع PyInterpreterState_Main() اشارهگری به وضعیت آن برمیگرداند.
میتوانید با استفاده از تابع PyThreadState_Swap() بین زیرمفسرها جابهجا شوید. میتوانید آنها را با استفاده از توابع زیر ایجاد و نابود کنید:
-
type PyInterpreterConfig¶
ساختاری که بیشتر پارامترهای لازم برای پیکربندی یک زیرمفسر را در بر دارد. مقادیر آن تنها در
Py_NewInterpreterFromConfig()استفاده میشوند و هرگز توسط رانتایم تغییر داده نمیشوند.اضافه شده در نسخهی 3.12.
فیلدهای ساختار:
-
int use_main_obmalloc¶
اگر این مقدار
0باشد، زیرمفسر از وضعیت تخصیصدهندهی «object» خود استفاده میکند. در غیر این صورت از وضعیت تخصیصدهندهی مفسر اصلی (به اشتراک) استفاده میکند.اگر این مقدار
0باشد، آنگاهcheck_multi_interp_extensionsباید1(ناصفر) باشد. اگر این مقدار1باشد، آنگاهgilنبایدPyInterpreterConfig_OWN_GILباشد.
-
int allow_fork¶
اگر این مقدار
0باشد، رانتایم از فورک کردن فرایند در هیچ نخی که زیرمفسر در حال حاضر در آن فعال است پشتیبانی نخواهد کرد. در غیر این صورت، فورک بدون محدودیت است.توجه داشته باشید که ماژول
subprocessوقتی فورک ممنوع است، همچنان کار میکند.
-
int allow_exec¶
اگر این مقدار
0باشد، رانتایم در هیچ نخی که زیرمفسر در حال حاضر در آن فعال است، از جایگزینی فرایند جاری از طریق exec (مثلاًos.execv()) پشتیبانی نخواهد کرد. در غیر این صورت، exec بدون محدودیت است.توجه داشته باشید که ماژول
subprocessهنگامی که exec ممنوع است، همچنان کار میکند.
-
int allow_threads¶
اگر این مقدار
0باشد، ماژولthreadingزیرمفسر نخها را ایجاد نمیکند. در غیر این صورت، نخها مجاز هستند.
-
int allow_daemon_threads¶
اگر این مقدار
0باشد، ماژولthreadingدر زیرمفسر نخهای دِیمِن ایجاد نمیکند. در غیر این صورت، نخهای دِیمِن مجاز هستند (تا زمانی کهallow_threadsناصفر باشد).
-
int check_multi_interp_extensions¶
اگر این مقدار
0باشد، همه ماژولهای توسعهای، از جمله ماژولهای قدیمی (با مقداردهی اولیه تکمرحلهای)، میتوانند در هر نخی که زیرمفسر در حال حاضر در آن فعال است ایمپورت شوند. در غیر این صورت، فقط ماژولهای توسعهای با مقداردهی اولیه چندمرحلهای (نگاه کنید به PEP 489) میتوانند ایمپورت شوند. (همچنینPy_mod_multiple_interpretersرا ببینید.)اگر
use_main_obmallocبرابر0باشد، این مقدار باید1(ناصفر) باشد.
-
int gil¶
این، عملکرد قفل مفسر سراسری را برای زیرمفسر تعیین میکند. این میتواند یکی از موارد زیر باشد:
-
PyInterpreterConfig_DEFAULT_GIL¶
از انتخاب پیشفرض (
PyInterpreterConfig_SHARED_GIL) استفاده کنید.
-
PyInterpreterConfig_SHARED_GIL¶
استفاده از (اشتراکگذاری) قفل مفسر سراسریِ مفسر اصلی.
-
PyInterpreterConfig_OWN_GIL¶
از قفل مفسر سراسریِ خودِ زیرمفسر استفاده کنید.
اگر این مقدار
PyInterpreterConfig_OWN_GILباشد، آنگاهPyInterpreterConfig.use_main_obmallocباید0باشد.-
PyInterpreterConfig_DEFAULT_GIL¶
-
int use_main_obmalloc¶
-
PyStatus Py_NewInterpreterFromConfig(PyThreadState **tstate_p, const PyInterpreterConfig *config)¶
یک زیرمفسر جدید ایجاد کنید. این یک محیط (تقریباً) کاملاً جداگانه برای اجرای کد پایتون است. بهویژه، مفسر جدید نسخههای جداگانه و مستقلی از تمام ماژولهای ایمپورتشده دارد، از جمله ماژولهای بنیادی
builtins،__main__وsys. جدول ماژولهای بارگذاریشده (sys.modules) و مسیر جستجوی ماژول (sys.path) نیز جداگانه هستند. محیط جدید متغیرsys.argvندارد. این محیط اشیاء پروندهی جدید جریانهای ورودی/خروجی استانداردsys.stdin،sys.stdoutوsys.stderrرا دارد (البته اینها به همان توصیفگرهای پروندهی زیرین ارجاع میدهند).config دادهشده، گزینههایی را که مفسر با آنها راهاندازی میشود کنترل میکند.
در صورت موفقیت، tstate_p به نخستین وضعیت نخ ایجادشده در زیرمفسر جدید تنظیم میشود. این وضعیت نخ متصل است. توجه داشته باشید که هیچ نخ واقعی ایجاد نمیشود؛ بحث درباره وضعیتهای نخ را در ادامه ببینید. اگر ایجاد مفسر جدید ناموفق باشد، tstate_p به
NULLتنظیم میشود؛ هیچ استثنایی تنظیم نمیشود، زیرا وضعیت استثنا در وضعیت نخ متصل ذخیره میشود که ممکن است وجود نداشته باشد.مانند تمام توابع دیگر Python/C API، باید پیش از فراخوانی این تابع، یک attached thread state موجود باشد، اما ممکن است هنگام بازگشت جدا شده باشد. در صورت موفقیت، وضعیت نخ بازگرداندهشده متصل خواهد بود. اگر زیرمفسر (sub-interpreter) با GIL اختصاصی خودش ایجاد شود، attached thread state مفسر فراخواننده جدا خواهد شد. هنگامی که تابع بازمیگردد، thread state مفسر جدید به نخ فعلی متصل خواهد بود و attached thread state مفسر قبلی جدا باقی خواهد ماند.
اضافه شده در نسخهی 3.12.
زیرمفسرها زمانی بیشترین کارایی را دارند که از یکدیگر ایزوله باشند و برخی از قابلیتها محدود باشند:
PyInterpreterConfig config = { .use_main_obmalloc = 0, .allow_fork = 0, .allow_exec = 0, .allow_threads = 1, .allow_daemon_threads = 0, .check_multi_interp_extensions = 1, .gil = PyInterpreterConfig_OWN_GIL, }; PyThreadState *tstate = NULL; PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); if (PyStatus_Exception(status)) { Py_ExitStatusException(status); }
توجه داشته باشید که پیکربندی فقط بهطور گذرا استفاده میشود و تغییر داده نمیشود. در طول مقداردهی اولیه، مقادیر پیکربندی به مقادیر مختلف
PyInterpreterStateتبدیل میشوند. ممکن است یک نسخهی فقطخواندنی از پیکربندی بهطور داخلی رویPyInterpreterStateذخیره شود.ماژولهای توسعهای بین (زیر-)مفسرها به شرح زیر به اشتراک گذاشته میشوند:
برای ماژولهایی که از مقداردهی اولیه چندمرحلهای استفاده میکنند، مانند
PyModule_FromDefAndSpec()، یک شیء ماژول جداگانه برای هر مفسر ایجاد و مقداردهی اولیه میشود. تنها متغیرهای ایستا و سراسری در سطح C بین این اشیاء ماژول به اشتراک گذاشته میشوند.برای ماژولهایی که از مقداردهی اولیه تکفازی قدیمی استفاده میکنند، مانند
PyModule_Create()، اولین باری که یک ماژول توسعهای خاص ایمپورت میشود، بهطور عادی مقداردهی اولیه میشود و یک کپی (سطحی) از دیکشنری ماژول آن ذخیره میشود. وقتی همان ماژول توسعهای توسط (زیر)مفسر دیگری ایمپورت میشود، یک ماژول جدید مقداردهی اولیه میشود و با محتویات این کپی پر میشود؛ تابعinitماژول توسعهای فراخوانی نمیشود. بنابراین، اشیاء موجود در دیکشنری ماژول در نهایت میان (زیر)مفسرها به اشتراک گذاشته میشوند، که ممکن است باعث رفتار ناخواسته شود (به باگها و هشدارها در ادامه مراجعه کنید).توجه داشته باشید که این مورد با آنچه هنگام ایمپورت شدن یک ماژول توسعهای پس از بازمقداردهی اولیهی کامل مفسر از طریق فراخوانی
Py_FinalizeEx()وPy_Initialize()رخ میدهد، متفاوت است؛ در آن صورت، تابعinitmoduleماژول توسعهای دوباره فراخوانی میشود. مانند مقداردهی اولیهی چندمرحلهای، این بدان معناست که تنها متغیرهای ایستا و سراسری در سطح C بین این ماژولها مشترک هستند.
-
PyThreadState *Py_NewInterpreter(void)¶
- قسمتی از ABI پایدار.
یک زیرمفسر (sub-interpreter) جدید ایجاد کنید. این در اصل صرفاً پوششی در اطراف
Py_NewInterpreterFromConfig()با پیکربندیای است که رفتار موجود را حفظ میکند. نتیجه، یک زیرمفسر غیرمجزا (unisolated) است که قفل مفسر سراسری (GIL) را با مفسر اصلی به اشتراک میگذارد، اجازهی fork/exec را میدهد، اجازهی نخهای دِیمِن را میدهد و اجازهی ماژولهای مقداردهی اولیهی تکفازی (single-phase init) را میدهد.
-
void Py_EndInterpreter(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
(زیر)مفسری را نابود میکند که وضعیت نخ دادهشده آن را نمایندگی میکند. وضعیت نخ دادهشده باید متصل باشد. هنگامی که فراخوانی بازگردد، هیچ وضعیت نخ متصل وجود نخواهد داشت. تمام وضعیتهای نخ مرتبط با این مفسر نابود میشوند.
Py_FinalizeEx()تمام زیرمفسرهایی را که تا آن لحظه بهطور صریح نابود نشدهاند، از بین میبرد.
قفل مفسر سراسری (GIL) بهازای هر مفسر¶
اضافه شده در نسخهی 3.12.
با استفاده از Py_NewInterpreterFromConfig() میتوانید یک زیرمفسر ایجاد کنید که بهطور کامل از سایر مفسرها جداشده باشد، از جمله با داشتن قفل مفسر سراسری مخصوص به خود. مهمترین مزیت این جداسازی این است که چنین مفسری میتواند بدون مسدود شدن توسط مفسرهای دیگر و بدون مسدود کردن هیچ مفسر دیگری، کد پایتون را اجرا کند. بدین ترتیب، یک فرایند پایتون واحد میتواند هنگام اجرای کد پایتون واقعاً از چندین هستهی CPU بهره ببرد. این جداسازی همچنین بهجای صرفاً استفاده از نخها، رویکردی متفاوت به همزمانی را ترغیب میکند. (به PEP 554 و PEP 684 مراجعه کنید.)
استفاده از مفسر مجزا نیازمند هوشیاری در حفظ آن جداسازی است. این بهویژه به آن معناست که هیچ شیئی یا وضعیت تغییرپذیری را نباید بدون تضمینهایی دربارهی نخایمنی به اشتراک گذاشت. حتی اشیائی که در سایر جهات تغییرناپذیرند (مانند None و (1, 5)) بهطور معمول نمیتوانند به دلیل شمارش ارجاع به اشتراک گذاشته شوند. یک راهحل ساده اما کمکارآمدتر برای این مسئله، استفاده از یک قفل سراسری در پیرامون تمامی استفادهها از یک وضعیت (یا شیء) است. بهطور جایگزین، اشیاء عملاً تغییرناپذیر (مانند اعداد صحیح یا رشتهها) را میتوان با وجود شمارش ارجاعشان، از طریق نامیرا کردن آنها ایمن ساخت. در واقع، این کار برای تکنمونههای توکار، اعداد صحیح کوچک و تعدادی از اشیاء توکار دیگر انجام شده است.
اگر جداسازی را حفظ کنید، به محاسبات چندهستهای واقعی بدون پیچیدگیهایی که با نخبندی آزاد همراه هستند دسترسی خواهید داشت. در صورت عدم حفظ جداسازی، با تمام پیامدهای نخبندی آزاد از جمله رقابتها و فروپاشیهایی که اشکالزدایی آنها دشوار است روبهرو خواهید شد.
علاوه بر این، یکی از چالشهای اصلی استفاده از چندین مفسر جداشده این است که چگونه میتوان بهطور امن (بدون بر هم زدن جداسازی) و کارآمد بین آنها ارتباط برقرار کرد. رانتایم و کتابخانه استاندارد هنوز هیچ رویکرد استانداردی برای این کار ارائه نمیدهند. یک ماژول آینده در کتابخانه استاندارد میتواند به کاهش تلاش لازم برای حفظ جداسازی کمک کند و ابزارهای مؤثری برای ارتباط (و اشتراکگذاری) دادهها بین مفسرها در اختیار بگذارد.
اشکالها و هشدارها¶
از آنجا که زیرمفسرها (و مفسر اصلی) بخشی از یک فرایند واحد هستند، جداسازی میان آنها بینقص نیست --- برای مثال، با استفاده از عملیاتهای سطح پایین پرونده مانند os.close() میتوانند (بهطور تصادفی یا خرابکارانه) بر پروندههای باز یکدیگر تأثیر بگذارند. به دلیل نحوهی اشتراکگذاری ماژولهای توسعهای میان (زیر)مفسرها، ممکن است برخی از ماژولهای توسعهای بهدرستی کار نکنند؛ احتمال این امر بهویژه هنگام استفاده از مقداردهی اولیهی تکفازی یا متغیرهای سراسری (ایستا) بیشتر است. میتوان اشیایی را که در یک زیرمفسر ایجاد شدهاند، در فضای نام (زیر)مفسر دیگری قرار داد؛ بهتر است در صورت امکان از این کار پرهیز شود.
باید با دقت ویژه از اشتراکگذاری توابع، متدها، نمونهها یا کلاسهای تعریفشده توسط کاربر بین زیرمفسرها جلوگیری شود، زیرا عملیات ایمپورت که توسط چنین اشیایی اجرا میشوند، ممکن است بر دیکشنری ماژولهای بارگذاریشدهی (زیر)مفسر اشتباه تأثیر بگذارد. به همان اندازه مهم است که از اشتراکگذاری اشیایی که موارد فوق از طریق آنها قابل دسترسیاند، جلوگیری شود.
همچنین توجه داشته باشید که ترکیب این قابلیت با APIهای PyGILState_* امری حساس است، زیرا این APIها تناظر یکبهیک بین وضعیتهای نخ پایتون و نخهای سطح سیستمعامل را فرض میکنند؛ فرضی که با وجود زیرمفسرها نقض میشود. قویاً توصیه میشود که زیرمفسرها را بین یک جفت فراخوانی متناظر PyGILState_Ensure() و PyGILState_Release() عوض نکنید. علاوه بر این، ماژولهای توسعهای (مانند ctypes) که از این APIها برای فراهم کردن امکان فراخوانی کد پایتون از نخهایی که توسط پایتون ایجاد نشدهاند استفاده میکنند، احتمالاً هنگام استفاده از زیرمفسرها از کار میافتند.
APIهای سطحبالا¶
-
type PyInterpreterState¶
- قسمتی از API محدود (بهعنوان یک ساختار مبهم).
این ساختار داده وضعیت مشترک بین تعدادی از نخهای همکاریکننده را نشان میدهد. نخهایی که به یک مفسر واحد تعلق دارند، مدیریت ماژول خود و چند مورد داخلی دیگر را به اشتراک میگذارند. در این ساختار هیچ عضو عمومی وجود ندارد.
نخهای متعلق به مفسرهای مختلف در ابتدا هیچ چیزی را به اشتراک نمیگذارند، بهجز وضعیت فرایند مانند حافظهی در دسترس، توصیفگرهای پروندهی باز و مواردی از این دست. قفل مفسر سراسری نیز میان همهی نخها مشترک است، صرفنظر از اینکه به کدام مفسر تعلق دارند.
تغییر یافته در نسخهی 3.12: PEP 684 امکان قفل مفسر سراسری برای هر مفسر را معرفی کرد.
Py_NewInterpreterFromConfig()را ببینید.
-
PyInterpreterState *PyInterpreterState_Get(void)¶
- قسمتی از ABI پایدار از نسخهی 3.9.
مفسر جاری را برمیگرداند.
اگر attached thread state وجود نداشته باشد، خطای مهلک صادر میکند. این تابع نمیتواند NULL را بازگرداند.
اضافه شده در نسخهی 3.9.
-
int64_t PyInterpreterState_GetID(PyInterpreterState *interp)¶
- قسمتی از ABI پایدار از نسخهی 3.7.
شناسه یکتای مفسر را برمیگرداند. اگر در انجام این کار خطایی رخ دهد،
-1برگردانده میشود و خطا تنظیم میشود.فراخوانکننده باید یک attached thread state داشته باشد.
اضافه شده در نسخهی 3.7.
-
PyObject *PyInterpreterState_GetDict(PyInterpreterState *interp)¶
- مقدار بازگشتی: مرجع امانتی. قسمتی از ABI پایدار از نسخهی 3.8.
یک دیکشنری برمیگرداند که در آن میتوان دادههای خاص مفسر را ذخیره کرد. اگر این تابع
NULLرا برگرداند، هیچ استثنایی ایجاد نشده است و فراخواننده باید فرض کند که هیچ دیکشنری خاص مفسری در دسترس نیست.این جایگزینی برای
PyModule_GetState()نیست؛ ماژولهای توسعهای باید از آن برای ذخیرهسازی اطلاعات وضعیت مخصوص مفسر استفاده کنند.دیکشنری برگرداندهشده از مفسر امانت گرفته شده است و تا زمان خاموش شدن مفسر معتبر است.
اضافه شده در نسخهی 3.8.
-
typedef PyObject *(*_PyFrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)¶
نوع یک تابع ارزیابی فریم.
پارامتر throwflag توسط متد
throw()تولیدگرها استفاده میشود: اگر ناصفر باشد، استثنای جاری مدیریت میشود.تغییر یافته در نسخهی 3.9: این تابع اکنون پارامتر tstate را میپذیرد.
تغییر یافته در نسخهی 3.11: پارامتر frame از
PyFrameObject*به_PyInterpreterFrame*تغییر کرد.
-
_PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp)¶
تابع ارزیابی فریم را دریافت کنید.
به PEP 523 «افزودن یک API ارزیابی فریم به سیپایتون» مراجعه کنید.
اضافه شده در نسخهی 3.9.
-
void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame)¶
تابع ارزیابی فریم را تنظیم میکند.
به PEP 523 «افزودن یک API ارزیابی فریم به سیپایتون» مراجعه کنید.
اضافه شده در نسخهی 3.9.
APIهای سطح پایین¶
تمام توابع زیر باید پس از Py_Initialize() فراخوانی شوند.
تغییر یافته در نسخهی 3.7: Py_Initialize() اکنون قفل مفسر سراسری را مقداردهی اولیه میکند و یک وضعیت نخ متصل تنظیم میکند.
-
PyInterpreterState *PyInterpreterState_New()¶
- قسمتی از ABI پایدار.
یک شیء وضعیت مفسر جدید ایجاد میکند. نیازی به attached thread state نیست، اما در صورت ضرورت سریالسازی فراخوانیهای این تابع، میتواند بهصورت اختیاری وجود داشته باشد.
یک رویداد حسابرسی
cpython.PyInterpreterState_Newرا بدون هیچ آرگومانی ایجاد میکند.
-
void PyInterpreterState_Clear(PyInterpreterState *interp)¶
- قسمتی از ABI پایدار.
تمام اطلاعات در یک شیء وضعیت مفسر را بازنشانی میکند. باید یک attached thread state برای مفسر وجود داشته باشد.
رویداد حسابرسی
cpython.PyInterpreterState_Clearرا بدون هیچ آرگومانی ایجاد میکند.
-
void PyInterpreterState_Delete(PyInterpreterState *interp)¶
- قسمتی از ABI پایدار.
یک شیء وضعیت مفسر را نابود میکند. برای مفسر هدف نباید attached thread state وجود داشته باشد. وضعیت مفسر باید پیشتر با فراخوانی
PyInterpreterState_Clear()بازنشانی شده باشد.
پشتیبانی از اشکالزدای پیشرفته¶
این توابع تنها برای استفاده توسط ابزارهای پیشرفتهی اشکالزدایی در نظر گرفته شدهاند.
-
PyInterpreterState *PyInterpreterState_Head()¶
شیء وضعیت مفسر در ابتدای فهرست همهی چنین اشیایی را بازمیگرداند.
-
PyInterpreterState *PyInterpreterState_Main()¶
شیء وضعیت مفسر اصلی را برمیگرداند.
-
PyInterpreterState *PyInterpreterState_Next(PyInterpreterState *interp)¶
شیء وضعیت مفسرِ بعدی پس از interp را از فهرست همهی چنین اشیایی برمیگرداند.
-
PyThreadState *PyInterpreterState_ThreadHead(PyInterpreterState *interp)¶
اشارهگر به نخستین شیء
PyThreadStateدر فهرست نخهای مرتبط با مفسر interp را برمیگرداند.
-
PyThreadState *PyThreadState_Next(PyThreadState *tstate)¶
شیء وضعیت نخ بعدی را پس از tstate از فهرست همهی اشیاء از این دست که به همان شیء
PyInterpreterStateتعلق دارند، برمیگرداند.