وضعیتهای نخ و قفل مفسر سراسری¶
مگر در free-threaded build از CPython، مفسر پایتون بهطور کلی نخایمن نیست. به منظور پشتیبانی از برنامههای چندنخی پایتون، یک قفل سراسری به نام global interpreter lock یا GIL وجود دارد که باید پیش از دسترسی به اشیای پایتون، توسط یک نخ نگه داشته شود. بدون این قفل، حتی سادهترین عملیاتها میتوانند در یک برنامهی چندنخی مشکل ایجاد کنند: برای مثال، وقتی دو نخ همزمان شمارش ارجاع یک شیء واحد را افزایش میدهند، ممکن است شمارش ارجاع در نهایت به جای دو بار، فقط یک بار افزایش یابد.
از این رو، تنها نخی که قفل مفسر سراسری (GIL) را در اختیار دارد، میتواند روی اشیای پایتون عمل کند یا C API پایتون را فراخوانی کند.
برای شبیهسازی همزمانی، مفسر بهطور منظم تلاش میکند نخها را بین دستورالعملهای بایتکد تعویض کند (به sys.setswitchinterval() مراجعه کنید). به همین دلیل است که قفلها برای نخایمنی در کد پایتون خالص نیز ضروری هستند.
علاوه بر این، قفل مفسر سراسری در اطراف عملیات ورودی/خروجی (I/O) مسدودکننده، مانند خواندن از یک پرونده یا نوشتن در آن، آزاد میشود. از طریق C API، این کار با جدا کردن وضعیت نخ انجام میشود.
مفسر پایتون برخی از اطلاعات نخمحلی را درون ساختار دادهای به نام PyThreadState نگه میدارد که به آن وضعیت نخ گفته میشود. هر نخ یک اشارهگر نخمحلی به یک PyThreadState دارد؛ وضعیت نخی که این اشارهگر به آن ارجاع میدهد، متصل در نظر گرفته میشود.
یک نخ در هر لحظه تنها میتواند یک attached thread state داشته باشد. وضعیت نخ متصل معمولاً معادل با در اختیار داشتن GIL است، بهجز در ساختهای نخآزاد. در ساختهایی که GIL فعال است، متصل کردن یک وضعیت نخ تا زمانی که بتوان GIL را به دست آورد، مسدود میماند. با این حال، حتی در ساختهایی که GIL غیرفعال است، همچنان داشتن یک وضعیت نخ متصل الزامی است، زیرا مفسر باید پیگیری کند که کدام نخها ممکن است به اشیای پایتون دسترسی داشته باشند.
نکته
حتی در ساخت نخآزاد، پیوست کردن وضعیت نخ ممکن است مسدود شود، زیرا قفل مفسر سراسری میتواند دوباره فعال شود یا نخها ممکن است بهطور موقت معلق شوند (مانند هنگام زبالهروبی).
بهطور کلی، هنگام استفاده از API زبان C پایتون، همیشه یک وضعیت نخ متصل وجود خواهد داشت، از جمله در حین تعبیه و هنگام پیادهسازی متدها؛ بنابراین بهندرت پیش میآید که لازم باشد خودتان وضعیت نخ ایجاد کنید. تنها در برخی موارد خاص، مانند داخل بلوک Py_BEGIN_ALLOW_THREADS یا در یک نخ تازه، نخ وضعیت نخ متصل نخواهد داشت. اگر مطمئن نیستید، بررسی کنید که آیا PyThreadState_GetUnchecked() مقدار NULL برمیگرداند یا خیر.
اگر مشخص شد که واقعاً به ایجاد یک وضعیت نخ نیاز دارید، توصیه میشود از PyThreadState_Ensure() یا PyThreadState_EnsureFromView() استفاده کنید، که وضعیت نخ را برای شما مدیریت خواهند کرد.
جدا کردن وضعیت نخ از کد توسعهای¶
بیشتر کدهای توسعهای که وضعیت نخ را دستکاری میکنند، ساختار سادهی زیر را دارند:
وضعیت نخ را در یک متغیر محلی ذخیره کنید.
... یک عملیات ورودی/خروجی مسدودکننده انجام دهید ...
وضعیت نخ را از متغیر محلی بازیابی کنید.
این چنان رایج است که جفتی از ماکروها برای سادهسازی آن وجود دارد:
Py_BEGIN_ALLOW_THREADS
... یک عملیات ورودی/خروجی مسدودکننده انجام دهید ...
Py_END_ALLOW_THREADS
ماکروی Py_BEGIN_ALLOW_THREADS یک بلوک جدید باز میکند و یک متغیر محلی پنهان اعلان میکند؛ ماکروی Py_END_ALLOW_THREADS بلوک را میبندد.
بلوک بالا به کد زیر بسط مییابد:
PyThreadState *_save;
_save = PyEval_SaveThread();
... یک عملیات ورودی/خروجی مسدودکننده انجام دهید ...
PyEval_RestoreThread(_save);
نحوه کار این توابع به این صورت است:
متصل بودن وضعیت نخ نشان میدهد که قفل مفسر سراسری (GIL) برای مفسر نگه داشته شده است. برای جدا کردن آن، PyEval_SaveThread() فراخوانی میشود و نتیجه در یک متغیر محلی ذخیره میشود.
با جدا کردن وضعیت نخ، قفل مفسر سراسری (GIL) آزاد میشود که به نخهای دیگر اجازه میدهد در حالی که نخ فعلی در حال انجام عملیات ورودی/خروجی مسدودکننده است، به مفسر متصل شده و اجرا شوند. هنگامی که عملیات ورودی/خروجی به پایان رسید، وضعیت نخ قبلی با فراخوانی PyEval_RestoreThread() دوباره متصل میشود؛ این تابع تا زمانی که بتوان قفل مفسر سراسری را به دست آورد، منتظر میماند.
نکته
انجام عملیات ورودی/خروجی مسدودکننده رایجترین مورد استفاده برای جداسازی وضعیت نخ است، اما فراخوانی آن روی کد بومی طولانیمدتی که نیازی به دسترسی به اشیاء پایتون یا C API پایتون ندارد نیز مفید است. برای مثال، ماژولهای استاندارد zlib و hashlib هنگام فشردهسازی یا هش کردن دادهها، وضعیت نخ را جدا میکنند.
در یک ساخت نخآزاد، قفل مفسر سراسری (GIL) معمولاً منتفی است، اما جدا کردن وضعیت نخ همچنان الزامی است، زیرا مفسر بهطور دورهای نیاز دارد همهی نخها را مسدود کند تا نمای سازگار از اشیای پایتون را بدون خطر شرایط رقابتی به دست آورد. برای مثال، سیپایتون در حال حاضر هنگام اجرای زبالهروب، همهی نخها را برای مدت کوتاهی معلق میکند.
APIها¶
ماکروهای زیر معمولاً بدون نقطهویرگول انتهایی استفاده میشوند؛ نمونههای استفاده را در توزیع کد منبع پایتون جستوجو کنید.
نکته
این ماکروها در ساخت نخآزاد همچنان برای جلوگیری از بنبستها ضروری هستند.
-
Py_BEGIN_ALLOW_THREADS¶
- This macro is available when compiling for the ABI پایدار.
این ماکرو به { PyThreadState *_save; _save = PyEval_SaveThread(); بسط مییابد. توجه داشته باشید که این ماکرو یک آکولاد باز در بر دارد و باید با ماکروی Py_END_ALLOW_THREADS که پس از آن میآید جفت شود. برای بحث بیشتر دربارهی این ماکرو به بالا مراجعه کنید.
-
Py_END_ALLOW_THREADS¶
- This macro is available when compiling for the ABI پایدار.
این ماکرو به PyEval_RestoreThread(_save); } بسط مییابد. توجه داشته باشید که این ماکرو شامل یک آکولاد بسته است و باید با یک ماکروی Py_BEGIN_ALLOW_THREADS پیشین جفت شود. برای بحث بیشتر دربارهی این ماکرو، به بالا مراجعه کنید.
-
Py_BLOCK_THREADS¶
- This macro is available when compiling for the ABI پایدار.
این ماکرو به PyEval_RestoreThread(_save); بسط مییابد: معادل Py_END_ALLOW_THREADS بدون آکولاد پایانی است.
-
Py_UNBLOCK_THREADS¶
- This macro is available when compiling for the ABI پایدار.
این ماکرو به _save = PyEval_SaveThread(); بسط مییابد: معادل Py_BEGIN_ALLOW_THREADS بدون آکولاد باز و اعلان متغیر است.
استفاده از API زبان C از نخهای خارجی¶
وقتی نخها با استفاده از APIهای اختصاصی پایتون (مانند ماژول threading) ایجاد میشوند، یک وضعیت نخ بهطور خودکار با آنها مرتبط میشود؛ با این حال، وقتی یک نخ از کد بومی ایجاد شود (برای مثال، توسط یک کتابخانه شخص ثالث با مدیریت نخ اختصاصی خودش)، وضعیت نخ متصلشدهای ندارد.
اگر لازم باشد کد پایتون را از این نخها فراخوانی کنید (اغلب این کار بخشی از یک API کالبک ارائهشده توسط کتابخانه شخص ثالث مذکور خواهد بود)، باید ابتدا با ایجاد یک وضعیت نخ جدید و پیوست دادن آن، این نخها را در مفسر ثبت کنید.
سادهترین راه برای انجام این کار استفاده از PyThreadState_Ensure() یا PyThreadState_EnsureFromView() است.
نکته
این تابعها به آرگومانی نیاز دارند که به مفسر مورد نظر اشاره کند؛ چنین اشارهگری را میتوان از طریق فراخوانی PyInterpreterGuard_FromCurrent() (برای PyThreadState_Ensure) یا PyInterpreterView_FromCurrent() (برای PyThreadState_EnsureFromView) از تابعی که نخ را ایجاد میکند، بهدست آورد. اگر هیچ اشارهگری در دسترس نباشد (مانند زمانی که کتابخانهی نخ بومیِ دادهشده آرگومان دادهای فراهم نمیکند)، میتوانید از PyInterpreterView_FromMain() برای دریافت یک نما برای مفسر اصلی استفاده کنید، اما توجه داشته باشید که این کار کد را با زیرمفسرها ناسازگار میکند.
برای مثال:
// The return value of PyInterpreterGuard_FromCurrent() from the
// function that created this thread.
PyInterpreterGuard *guard = thread_data->guard;
// Create a new thread state for the interpreter.
PyThreadStateToken *token = PyThreadState_Ensure(guard);
if (token == NULL) {
PyInterpreterGuard_Close(guard);
return;
}
// We have a valid thread state -- perform Python actions here.
result = CallSomeFunction();
// Evaluate result or handle exceptions.
// Release the thread state. No calls to the C API are allowed beyond this
// point.
PyThreadState_Release(token);
PyInterpreterGuard_Close(guard);
به خاطر داشته باشید که فراخوانی PyThreadState_Ensure ممکن است همیشه یک وضعیت نخ جدید ایجاد نکند و فراخوانی PyThreadState_Release ممکن است همیشه آن را جدا نکند. این تابعها ممکن است یک وضعیت نخ متصلِ موجود را دوباره استفاده کنند، یا وضعیت نخی را که قبلاً برای نخ فعلی متصل شده بود، دوباره متصل کنند.
استفادهی مجدد از وضعیت نخ در فراخوانیهای مکرر¶
ایجاد و نابود کردن یک PyThreadState رایگان نیست و در یک ساخت نخآزاد هزینهی بیشتری دارد. یک نخ خارجی که بارها مفسر را فراخوانی میکند -- برای مثال، یک نخ کارگر در یک استخر نخ بومی -- باید از ایجاد وضعیت نخ جدید در هر بار ورود و نابود کردن آن در هر بار خروج خودداری کند. در عوض، هنگامی که نخ شروع به کار میکند (یا به صورت تنبل در اولین فراخوانیاش به پایتون)، یک وضعیت نخ را راهاندازی کنید، آن را در اطراف هر فراخوانی متصل و جدا کنید، و هنگامی که نخ خارج میشود، آن را یک بار از بین ببرید.
وضعیت نخ را به صراحت با PyThreadState_New() مدیریت کنید و آن را با PyEval_RestoreThread() و PyEval_SaveThread() متصل و جدا کنید. این کار در سه مرحلهی مجزا، در نقاط مختلفی از چرخهی حیات نخ رخ میدهد.
هنگامی که نخ شروع به کار میکند، یک وضعیت نخ برای آن ایجاد کنید. interp مفسر هدف است که توسط کدی که این نخ را ایجاد کرده است، در حالی که یک وضعیت نخ متصل را نگه میداشت (برای مثال از طریق PyInterpreterState_Get())، گرفته شده است:
PyThreadState *tstate = PyThreadState_New(interp);
سپس، در هر فراخوانی به پایتون -- که ممکن است بارها در طول چرخهی حیات نخ رخ دهد -- وضعیت نخ را متصل کنید، فراخوانیهای API زبان C پایتون را که به آن نیاز دارند انجام دهید، و دوباره آن را جدا کنید تا نخ در حالی که کارهای غیرپایتونی انجام میدهد، GIL را نگه ندارد:
PyEval_RestoreThread(tstate);
result = CallSomeFunction(); /* فراخوانیهای API زبان C پایتون شما در اینجا قرار میگیرند */
PyEval_SaveThread();
هنگامی که کار نخ با فراخوانی پایتون به پایان رسید، وضعیت نخ را یک بار نابود کنید:
PyEval_RestoreThread(tstate);
PyThreadState_Clear(tstate);
PyThreadState_DeleteCurrent();
نقاط ورودی همهمنظوره برای فراخوانی از یک نخ خارجی -- یعنی PyThreadState_Ensure() و PyGILState_Ensure() قدیمی -- پایداری وضعیت نخ را تضمین نمیکنند: طول عمر وضعیت نخِ آنها عمدتاً توسط پیادهسازی تعریف میشود، بنابراین ممکن است یک جفت بهدستآوردن/آزاد کردنِ منطبق، در هر بار یک وضعیت نخ ایجاد و نابود کند. هر زمان که بهطور خاص میخواهید از یک وضعیت نخ در میان فراخوانیها استفاده مجدد کنید، همانطور که در اینجا نشان داده شده است، از PyThreadState_New() استفاده کنید.
کدی که نخ خارجی را ایجاد کرده است باید ترتیبی دهد که دنبالهی خاموشی قبل از خروج نخ و قبل از فراخوانی Py_FinalizeEx() اجرا شود. اگر نهاییسازی مفسر ابتدا آغاز شود، فراخوانی خاموشی PyEval_RestoreThread() به جای بازگشت، نخ را معلق میکند (نگاه کنید به احتیاطهای مربوط به نهاییسازی مفسر). اگر نخ بدون اجرای دنبالهی خاموشی خارج شود، وضعیت نخ برای بقیه روند پردازش نشت میکند.
اتصال و جداسازی وضعیتهای نخ¶
-
PyThreadStateToken *PyThreadState_Ensure(PyInterpreterGuard *guard)¶
- قسمتی از ABI پایدار از نسخهی 3.15.
اطمینان حاصل کنید که نخ دارای یک وضعیت نخ متصل برای مفسری است که توسط guard محافظت میشود، و بنابراین میتواند با خیال راحت آن مفسر را فراخوانی کند.
اگر نخ از قبل دارای یک وضعیت نخ متصل باشد، فراخوانی این تابع اشکالی ندارد، به شرطی که فراخوانی بعدی برای PyThreadState_Release() وجود داشته باشد که با این یکی مطابقت داشته باشد (به این معنا که فراخوانیهای «تودرتو» به این تابع مجاز هستند).
تأثیر تابع (در صورت وجود) توسط فراخوانی منطبق به PyThreadState_Release() معکوس خواهد شد.
هنگام خطا، این تابع NULL را بدون تنظیم هیچ استثنایی برمیگرداند. در این صورت PyThreadState_Release() را فراخوانی نکنید.
در صورت موفقیت، این تابع یک مقدار اشارهگر بازمیگرداند که باید به فراخوانی متناظر در PyThreadState_Release() پاس داده شود.
شرایطی که در آنها این تابع یک وضعیت نخ جدید ایجاد میکند، ناپایدار و وابسته به پیادهسازی در نظر گرفته میشوند. اگر نیاز دارید طول عمر دقیق یک وضعیت نخ را کنترل کنید، استفاده از PyThreadState_New() را در نظر بگیرید. با این حال، صرفاً به این دلیل که ممکن است طول عمر وضعیت نخ در نسخههای مختلف ناسازگار باشد، از این تابع اجتناب نکنید؛ تغییرات در این تابع با احتیاط و به شیوهای سازگار با گذشته انجام خواهد شد. بهویژه، ذخیرهسازی متغیرهای نخمحلی (thread-local) و وضعیتهای مشابه در نسخههای مختلف پایتون حفظ خواهد شد.
رفتار دقیق این که آیا این تابع یک وضعیت نخ جدید ایجاد میکند یا خیر، در ادامه شرح داده شده است، اما آگاه باشید که ممکن است در آینده تغییر کند.
ابتدا، این تابع بررسی میکند که آیا یک وضعیت نخ متصل حضور دارد یا خیر. اگر چنین باشد، این تابع سپس بررسی میکند که آیا مفسر آن وضعیت نخ با مفسر محافظتشده توسط guard مطابقت دارد یا خیر. اگر چنین باشد، این تابع صرفاً وضعیت نخ را به عنوان در حال استفاده توسط یک فراخوانی PyThreadState_Ensure علامتگذاری میکند و برمیگردد.
اگر هیچ وضعیت نخ متصلی وجود نداشته باشد، این تابع بررسی میکند که آیا هیچ وضعیت نخی توسط نخ جاری سیستمعامل استفاده شده است یا خیر. (این مقدار توسط PyGILState_GetThisThreadState() بازگردانده میشود.) اگر وجود داشته باشد، این تابع بررسی میکند که آیا مفسر آن وضعیت نخ با guard مطابقت دارد یا خیر. اگر مطابقت داشته باشد، مجدداً متصل شده و به عنوان استفادهشده علامتگذاری میشود.
در غیر این صورت، اگر هر دو مورد بالا با شکست مواجه شوند، یک وضعیت نخ جدید برای guard ایجاد میشود. سپس متصل شده و به عنوان متعلق به PyThreadState_Ensure علامتگذاری میشود.
اضافه شده در نسخهی 3.15.
-
PyThreadStateToken *PyThreadState_EnsureFromView(PyInterpreterView *view)¶
- قسمتی از ABI پایدار از نسخهی 3.15.
یک وضعیت نخ متصل برای مفسر ارجاعدادهشده توسط view را دریافت کنید.
رفتار و مقدار بازگشتی مشابه PyThreadState_Ensure() است؛ علاوه بر این، اگر تابع موفق باشد، مفسر ارجاعدادهشده توسط view بهطور ضمنی محافظت خواهد شد. این محافظ پس از فراخوانی متناظر PyThreadState_Release() آزاد خواهد شد.
اضافه شده در نسخهی 3.15.
-
void PyThreadState_Release(PyThreadStateToken *token)¶
- قسمتی از ABI پایدار از نسخهی 3.15.
یک فراخوانی PyThreadState_Ensure() یا PyThreadState_EnsureFromView() را خنثی کنید.
این تابع باید دقیقاً یک بار برای هر فراخوانی موفق Ensure، در حالی که token روی مقدار بازگشتی آن فراخوانی تنظیم شده است، فراخوانی شود.
وضعیتی که پیش از فراخوانی متناظر Ensure (در صورت وجود) متصل شده بود، هنگام بازگشت PyThreadState_Release() متصل خواهد شد.
رفتار دقیق این که آیا این تابع یک وضعیت نخ را حذف میکند یا خیر، ناپایدار و وابسته به پیادهسازی در نظر گرفته میشود.
در حال حاضر، این تابع یک شمارندهی داخلی را در وضعیت نخ متصل کاهش میدهد. اگر این شمارنده روزی به زیر صفر برسد، این تابع یک خطای مهلک (از طریق Py_FatalError()) صادر میکند.
اگر وضعیت نخ متصل متعلق به PyThreadState_Ensure باشد، با رسیدن شمارندهی داخلی به صفر، وضعیت نخ متصل از تخصیص خارج و حذف خواهد شد. در غیر این صورت، با رسیدن شمارنده به صفر اتفاقی نمیافتد.
اضافه شده در نسخهی 3.15.
-
type PyThreadStateToken¶
- قسمتی از ABI پایدار (بهعنوان یک ساختار مبهم) از نسخهی 3.15.
یک توکن کدر که از یک فراخوانی PyThreadState_Ensure() بازیابی شده و به یک فراخوانی متناظر PyThreadState_Release() پاس داده میشود.
APIهای وضعیت قفل مفسر سراسری¶
رابطهای برنامهنویسی کاربردی (API) زیر بهطور کلی با زیرمفسرها سازگار نیستند و فرایند را در طول نهاییسازی مفسر متوقف (Hang) خواهند کرد (به احتیاطهای مربوط به نهاییسازی مفسر مراجعه کنید). به همین دلیل، این APIها در پایتون ۳.۱۵ به نفع رابطهای جدید بهطور نرمافزاری منسوخ شدند.
-
type PyGILState_STATE¶
- قسمتی از ABI پایدار.
نوع مقداری که توسط PyGILState_Ensure() بازگردانده میشود و به PyGILState_Release() پاس داده میشود.
-
enumerator PyGILState_LOCKED¶
هنگام فراخوانی PyGILState_Ensure()، قفل مفسر سراسری از قبل گرفته شده بود.
-
enumerator PyGILState_UNLOCKED¶
قفل مفسر سراسری هنگام فراخوانی PyGILState_Ensure() در اختیار گرفته نشده بود.
-
PyGILState_STATE PyGILState_Ensure()¶
- قسمتی از ABI پایدار.
تضمین میکند که نخ فعلی، صرفنظر از وضعیت فعلی پایتون یا وضعیت نخ متصل، برای فراخوانی Python C API آماده باشد. یک نخ میتواند این تابع را به هر تعداد که مطلوب است فراخوانی کند، به شرط آنکه هر فراخوانی با یک فراخوانی PyGILState_Release() جفت شود. بهطور کلی، میتوان بین فراخوانیهای PyGILState_Ensure() و PyGILState_Release() از سایر APIهای مربوط به نخ استفاده کرد، به شرط آنکه وضعیت نخ پیش از Release() به وضعیت پیشین خود بازگردانی شود. برای نمونه، استفادهی معمول از ماکروهای Py_BEGIN_ALLOW_THREADS و Py_END_ALLOW_THREADS قابلقبول است.
مقدار بازگشتی، یک «دسته» مات برای attached thread state در زمان فراخوانی PyGILState_Ensure() است و باید به PyGILState_Release() پاس داده شود تا اطمینان حاصل شود که پایتون در همان وضعیت باقی میماند. هرچند فراخوانیهای بازگشتی مجاز هستند، این دستهها نمیتوانند به اشتراک گذاشته شوند؛ هر فراخوانی مجزای PyGILState_Ensure() باید دسته را برای فراخوانی خود به PyGILState_Release() ذخیره کند.
هنگامی که تابع برمیگردد، یک وضعیت نخ متصل وجود خواهد داشت و نخ قادر خواهد بود کد دلخواه پایتون را فراخوانی کند.
این تابع هیچ راهی برای بازگرداندن خطا ندارد. به همین دلیل، خطاها یا مهلک هستند (یعنی SIGABRT را ارسال کرده و فرایند را دچار فروپاشی میکنند؛ به Py_FatalError() مراجعه کنید)، یا نخ بهطور دائم مسدود خواهد شد (مانند طول نهاییسازی مفسر).
هشدار
فراخوانی این تابع هنگامی که مفسر در حال نهاییسازی است، نخ را به طور بینهایت معلق خواهد کرد که ممکن است باعث ایجاد بنبست شود. برای جزئیات بیشتر به احتیاطهای مربوط به نهاییسازی مفسر مراجعه کنید.
علاوه بر این، این تابع هنگامی که از نخهای خارجی استفاده میشود، معمولاً با زیرمفسرها کار نمیکند، زیرا این تابع راهی برای دانستن اینکه کدام مفسر نخ را ایجاد کرده است ندارد (و به همین دلیل، بهطور ضمنی مفسر اصلی را انتخاب خواهد کرد).
تغییر یافته در نسخهی 3.14: اگر در حین نهاییسازی مفسر فراخوانی شود، نخ فعلی را معلق میکند، نه اینکه آن را خاتمه دهد.
-
void PyGILState_Release(PyGILState_STATE)¶
- قسمتی از ABI پایدار.
هر منبعی که قبلاً کسب شده است را آزاد کنید. پس از این فراخوانی، وضعیت پایتون همانند وضعیت قبل از فراخوانی متناظر PyGILState_Ensure() خواهد بود (اما به طور کلی این وضعیت برای فراخواننده ناشناخته خواهد بود، از این رو استفاده از API وضعیت GIL توصیه میشود).
هر فراخوانی PyGILState_Ensure() باید با یک فراخوانی PyGILState_Release() روی همان نخ متناظر باشد.
-
PyThreadState *PyGILState_GetThisThreadState()¶
- قسمتی از ABI پایدار.
دریافت وضعیت نخ که اخیراً برای این نخ متصل شده است. (اگر جدیدترین وضعیت نخ حذف شده باشد، این تابع NULL را برمیگرداند.)
اگر فراخواننده دارای وضعیت نخ متصلی باشد، آن وضعیت برگردانده میشود.
به عبارت دیگر، این تابع وضعیت نخی را برمیگرداند که توسط PyGILState_Ensure() استفاده خواهد شد. اگر این تابع NULL را برگرداند، آنگاه PyGILState_Ensure یک وضعیت نخ جدید ایجاد خواهد کرد.
این تابع نمیتواند با شکست مواجه شود.
-
int PyGILState_Check()¶
اگر نخ فعلی دارای یک وضعیت نخ متصل باشد که با وضعیت نخ بازگرداندهشده توسط PyGILState_GetThisThreadState() مطابقت دارد، مقدار 1 را بازگردانید. اگر فراخواننده هیچ وضعیت نخ متصلی نداشته باشد یا به شکلی دیگر مطابقت نکند، این تابع 0 را برمیگرداند.
اگر فرایند کنونی پایتون تا به حال یک زیرمفسر ایجاد کرده باشد، این تابع همیشه مقدار 1 را برمیگرداند.
این تابع عمدتاً یک تابع کمکی/عیبیابی است.
هشدارهایی دربارهی fork()¶
نکتهی مهم دیگری که دربارهی نخها باید به آن توجه کرد، رفتار آنها در برابر فراخوانی C fork() است. در بیشتر سیستمهایی که fork() را دارند، پس از انشعاب یک فرایند، تنها نخی که انشعاب را انجام داده است، وجود خواهد داشت. این موضوع هم بر نحوهی مدیریت قفلها و هم بر تمام وضعیت ذخیرهشده در رانتایم سیپایتون تأثیر ملموسی دارد.
این واقعیت که فقط نخ «فعلی» باقی میماند، به این معناست که هر قفلی که توسط نخهای دیگر نگهداشته شده است، هرگز آزاد نخواهد شد. پایتون این مشکل را برای os.fork() با گرفتن قفلهای داخلی خود پیش از انشعاب و آزاد کردن آنها پس از آن حل میکند. علاوه بر این، پایتون هر اشیای قفل را در فرزند بازنشانی میکند. هنگام توسعه یا تعبیه پایتون، هیچ راهی برای اطلاع دادن به پایتون دربارهی قفلهای اضافی (غیرپایتونی) که باید پیش از انشعاب گرفته شوند یا پس از آن بازنشانی شوند، وجود ندارد. برای انجام همین کار، لازم است از امکانات سیستمعامل مانند pthread_atfork() استفاده شود. افزون بر این، هنگام توسعه یا تعبیه پایتون، فراخوانی مستقیم fork() بهجای فراخوانی آن از طریق os.fork() (و بازگشت به پایتون یا فراخوانی کدهای پایتون) ممکن است به بنبست منجر شود؛ زیرا یکی از قفلهای داخلی پایتون توسط نخی نگهداشته میشود که پس از انشعاب از کار افتاده است. PyOS_AfterFork_Child() میکوشد قفلهای لازم را بازنشانی کند، اما همیشه قادر به انجام آن نیست.
این واقعیت که همه نخهای دیگر از بین میروند، همچنین به این معناست که وضعیت رانتایم سیپایتون در آنجا باید بهدرستی پاکسازی شود، که os.fork() این کار را انجام میدهد. این به معنای نهایی کردن همه اشیاء دیگر PyThreadState متعلق به مفسر فعلی و همه اشیاء دیگر PyInterpreterState است. به دلیل این موضوع و ماهیت خاص مفسر «اصلی»، fork() باید فقط در نخ «اصلی» آن مفسر فراخوانی شود، جایی که رانتایم سراسری سیپایتون در ابتدا راهاندازی شده بود. تنها استثنا زمانی است که exec() بلافاصله پس از آن فراخوانی شود.
APIهای سطحبالا¶
اینها پرکاربردترین نوعها و توابع هنگام نوشتن توسعههای C چندنخی هستند.
-
type PyThreadState¶
- قسمتی از ABI پایدار (بهعنوان یک ساختار مبهم).
این ساختار داده، وضعیت یک نخ منفرد را نشان میدهد. تنها عضو دادهی عمومی عبارت است از:
-
PyInterpreterState *interp¶
وضعیت مفسر این نخ.
-
void PyEval_InitThreads()¶
- قسمتی از ABI پایدار.
تابع منسوخ که هیچ کاری انجام نمیدهد.
در پایتون 3.6 و نسخههای قدیمیتر، این تابع در صورت عدم وجود، قفل مفسر سراسری را ایجاد میکرد.
تغییر یافته در نسخهی 3.9: تابع اکنون هیچ کاری انجام نمیدهد.
تغییر یافته در نسخهی 3.7: این تابع اکنون توسط Py_Initialize() فراخوانی میشود، بنابراین دیگر لازم نیست خودتان آن را فراخوانی کنید.
تغییر یافته در نسخهی 3.2: این تابع دیگر نمیتواند پیش از Py_Initialize() فراخوانی شود.
-
PyThreadState *PyEval_SaveThread()¶
- قسمتی از ABI پایدار.
attached thread state را جدا کنید و آن را برگردانید. پس از بازگشت، نخ هیچ thread state نخواهد داشت.
-
void PyEval_RestoreThread(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
وضعیت نخ متصل را به tstate تنظیم کنید. وضعیت نخ ارسالشده نباید متصل باشد، در غیر این صورت بنبست رخ میدهد. پس از بازگشت، tstate متصل خواهد شد.
نکته
فراخوانی این تابع از یک نخ در زمانی که رانتایم در حال نهاییسازی است، باعث میشود نخ تا زمان خروج از برنامه معلق بماند، حتی اگر آن نخ توسط پایتون ایجاد نشده باشد. برای جزئیات بیشتر به احتیاطهای مربوط به نهاییسازی مفسر مراجعه کنید.
تغییر یافته در نسخهی 3.14: اگر در حین نهاییسازی مفسر فراخوانی شود، نخ فعلی را معلق میکند، نه اینکه آن را خاتمه دهد.
-
PyThreadState *PyThreadState_Get()¶
- قسمتی از ABI پایدار.
attached thread state را برمیگرداند. اگر نخ وضعیت نخ متصلی نداشته باشد (مانند زمانی که درون بلوک Py_BEGIN_ALLOW_THREADS هستیم)، خطای مهلک ایجاد میشود (تا فراخواننده نیازی به بررسی NULL نداشته باشد).
همچنین PyThreadState_GetUnchecked() را ببینید.
-
PyThreadState *PyThreadState_GetUnchecked()¶
مشابه PyThreadState_Get()، اما اگر NULL باشد، فرایند را با خطای مهلک از بین نمیبرد. فراخواننده مسئول بررسی NULL بودن نتیجه است.
اضافه شده در نسخهی 3.13: در پایتون 3.5 تا 3.12، این تابع خصوصی بود و به نام _PyThreadState_UncheckedGet() شناخته میشد.
-
PyThreadState *PyThreadState_Swap(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
attached thread state را روی tstate تنظیم میکند و thread state را که پیش از فراخوانی متصل بوده است، بازمیگرداند.
فراخوانی این تابع بدون وضعیت نخ متصل ایمن است؛ این تابع صرفاً NULL را برمیگرداند که نشان میدهد هیچ وضعیت نخ پیشینی وجود نداشته است.
نکته
مشابه PyGILState_Ensure()، این تابع در صورتی که رانتایم در حال نهاییسازی باشد، نخ را معلق میکند.
APIهای سطح پایین¶
-
PyThreadState *PyThreadState_New(PyInterpreterState *interp)¶
- قسمتی از ABI پایدار.
یک شیء وضعیت نخ جدید متعلق به شیء مفسر دادهشده ایجاد کنید. به یک وضعیت نخ متصل نیازی نیست.
-
void PyThreadState_Clear(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
تمام اطلاعات در یک شیء وضعیت نخ را بازنشانی میکند. tstate باید متصل باشد
تغییر یافته در نسخهی 3.9: این تابع اکنون کالبک PyThreadState.on_delete را فراخوانی میکند. پیشتر، این کار در PyThreadState_Delete() انجام میشد.
تغییر یافته در نسخهی 3.13: کالبک PyThreadState.on_delete حذف شد.
-
void PyThreadState_Delete(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
یک شیء وضعیت نخ را نابود میکند. tstate نباید به هیچ نخی متصل باشد. tstate باید پیشتر با فراخوانی PyThreadState_Clear() بازنشانی شده باشد.
-
void PyThreadState_DeleteCurrent(void)¶
وضعیت نخ متصل را جدا کنید (که باید پیشتر با یک فراخوانی از PyThreadState_Clear() بازنشانی شده باشد) و سپس آن را نابود کنید.
پس از بازگشت، هیچ thread stateای پیوست نخواهد شد.
-
PyFrameObject *PyThreadState_GetFrame(PyThreadState *tstate)¶
- قسمتی از ABI پایدار از نسخهی 3.10.
گرفتن فریم فعلی وضعیت نخ پایتون tstate.
یک ارجاع قوی را برمیگرداند. اگر هیچ فریمی هماکنون در حال اجرا نباشد، NULL را برمیگرداند.
همچنین ببینید PyEval_GetFrame().
tstate نباید NULL باشد، و باید متصل باشد.
-
uint64_t PyThreadState_GetID(PyThreadState *tstate)¶
- قسمتی از ABI پایدار از نسخهی 3.10.
شناسهی یکتای thread state را از وضعیت نخ پایتون tstate دریافت کنید.
tstate نباید NULL باشد، و باید متصل باشد.
-
PyInterpreterState *PyThreadState_GetInterpreter(PyThreadState *tstate)¶
- قسمتی از ABI پایدار از نسخهی 3.10.
مفسر وضعیت نخ پایتون tstate را برمیگرداند.
tstate نباید NULL باشد، و باید متصل باشد.
-
void PyThreadState_EnterTracing(PyThreadState *tstate)¶
ردگیری و پروفایلگیری را در وضعیت نخ پایتون tstate تعلیق میکند.
برای از سر گرفتن آنها، از تابع PyThreadState_LeaveTracing() استفاده کنید.
اضافه شده در نسخهی 3.11.
-
void PyThreadState_LeaveTracing(PyThreadState *tstate)¶
ردگیری و پروفایلگیری را در وضعیت نخ پایتون tstate که توسط تابع PyThreadState_EnterTracing() معلق شده است، از سر میگیرد.
همچنین توابع PyEval_SetTrace() و PyEval_SetProfile() را ببینید.
اضافه شده در نسخهی 3.11.
-
int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size)¶
این است API ناپایداراین ممکن است بدون هشدار در نسخههای جزئی تغییر کند.
آدرس شروع حفاظت پشته و اندازه حفاظت پشتهی یک وضعیت نخ پایتون را تنظیم میکند.
در صورت موفقیت، 0 برمیگرداند. در صورت شکست، یک استثنا تنظیم کرده و -1 برمیگرداند.
سیپایتون کنترل بازگشت را برای کد C به این صورت پیادهسازی میکند که وقتی متوجه میشود که پشته اجرای ماشین نزدیک به سرریز است، RecursionError ایجاد میکند. برای نمونه، تابع Py_EnterRecursiveCall() را ببینید. برای این کار، سیپایتون باید مکان پشته نخ فعلی را بداند که معمولاً آن را از سیستمعامل دریافت میکند. هنگامی که پشته تغییر میکند، مثلاً با استفاده از تکنیکهای تعویض زمینه مانند boost::context در کتابخانه Boost، باید PyUnstable_ThreadState_SetStackProtection() را فراخوانی کنید تا سیپایتون را از این تغییر مطلع کنید.
تابع PyUnstable_ThreadState_SetStackProtection() را یا پیش از تغییر پشته یا پس از آن فراخوانی کنید. بین این فراخوانی و تغییر پشته، هیچ فراخوانی دیگری به C API پایتون انجام ندهید.
برای بازگردانی این عملیات، به PyUnstable_ThreadState_ResetStackProtection() مراجعه کنید.
اضافه شده در نسخهی 3.15.
-
void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)¶
این است API ناپایداراین ممکن است بدون هشدار در نسخههای جزئی تغییر کند.
نشانی شروع حفاظت پشته و اندازه حفاظت پشته وضعیت نخ پایتون را به پیشفرضهای سیستمعامل بازنشانی میکند.
برای توضیح به PyUnstable_ThreadState_SetStackProtection() مراجعه کنید.
اضافه شده در نسخهی 3.15.
-
PyObject *PyThreadState_GetDict()¶
- مقدار بازگشتی: مرجع امانتی. قسمتی از ABI پایدار.
یک دیکشنری برمیگرداند که افزونهها میتوانند اطلاعات وضعیت مخصوص نخ را در آن ذخیره کنند. هر افزونه باید برای ذخیرهسازی وضعیت در دیکشنری، از یک کلید یکتا استفاده کند. اشکالی ندارد این تابع را زمانی که هیچ thread state متصل نیست، فراخوانی کنید. اگر این تابع NULL برگرداند، هیچ استثنایی مطرح نشده است و فراخوانکننده باید فرض کند که هیچ وضعیت نخی متصل نیست.
-
void PyEval_AcquireThread(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
tstate را، که نباید NULL یا از قبل پیوستشده باشد، به نخ فعلی پیوست میکند.
نخ فراخواننده نباید از قبل یک attached thread state داشته باشد.
نکته
فراخوانی این تابع از یک نخ در زمانی که رانتایم در حال نهاییسازی است، باعث میشود نخ تا زمان خروج از برنامه معلق بماند، حتی اگر آن نخ توسط پایتون ایجاد نشده باشد. برای جزئیات بیشتر به احتیاطهای مربوط به نهاییسازی مفسر مراجعه کنید.
تغییر یافته در نسخهی 3.14: اگر در حین نهاییسازی مفسر فراخوانی شود، نخ فعلی را معلق میکند، نه اینکه آن را خاتمه دهد.
PyEval_RestoreThread() تابعی در سطح بالاتر است که همیشه در دسترس است (حتی زمانی که نخها مقداردهی اولیه نشده باشند).
-
void PyEval_ReleaseThread(PyThreadState *tstate)¶
- قسمتی از ABI پایدار.
وضعیت نخ متصل را جدا میکند. آرگومان tstate، که نباید NULL باشد، تنها برای بررسی اینکه وضعیت نخ متصل را نمایندگی میکند استفاده میشود --- اگر چنین نباشد، خطای مهلکی گزارش میشود.
PyEval_SaveThread() تابعی در سطح بالاتر است که همیشه دسترسپذیر است (حتی زمانی که نخها مقداردهی اولیه نشده باشند).