اولیه‌های همگام‌سازی

API زبان C یک قفل متقابل پایه‌ای فراهم می‌کند.

type PyMutex

قفل متقابل. PyMutex باید برای نمایش وضعیت باز، با مقدار صفر مقداردهی اولیه شود. برای مثال:

PyMutex mutex = {0};

نباید نمونه‌های PyMutex کپی یا منتقل شوند. هم محتویات و هم آدرس یک PyMutex معنادار هستند و باید در مکانی ثابت و قابل‌نوشتن در حافظه باقی بماند.

توجه

PyMutex در حال حاضر یک بایت فضا اشغال می‌کند، اما اندازه‌ی آن باید ناپاید در نظر گرفته شود. این اندازه ممکن است در نسخه‌های آینده‌ی پایتون بدون دوره‌ی منسوخ‌شدن تغییر کند.

اضافه شده در نسخه‌ی 3.13.

void PyMutex_Lock(PyMutex *m)
Thread safety: Atomic.

قفل متقابل m را قفل می‌کند. اگر نخ دیگری از قبل آن را قفل کرده باشد، نخ فراخواننده تا زمانی که قفل متقابل باز شود، مسدود خواهد شد. در حال مسدود بودن، نخ وضعیت نخ را در صورت وجود به‌طور موقت جدا خواهد کرد.

اضافه شده در نسخه‌ی 3.13.

void PyMutex_Unlock(PyMutex *m)
Thread safety: Atomic.

قفل متقابل m را باز می‌کند. قفل متقابل باید در حالت قفل‌شده باشد --- در غیر این صورت، تابع خطای مهلک صادر خواهد کرد.

اضافه شده در نسخه‌ی 3.13.

int PyMutex_IsLocked(PyMutex *m)
Thread safety: Atomic.

اگر قفل متقابل m در حال حاضر قفل شده باشد، مقدار غیرصفر و در غیر این صورت صفر برمی‌گرداند.

توجه

این تابع فقط برای استفاده در ادعاها و اشکال‌زدایی در نظر گرفته شده است و نباید برای تصمیم‌گیری‌های کنترل همزمانی به کار رود، زیرا وضعیت قفل ممکن است بلافاصله پس از بررسی تغییر کند.

اضافه شده در نسخه‌ی 3.14.

API بخش بحرانی پایتون

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

بخش‌های بحرانی برای استفاده در نوع‌های سفارشی پیاده‌سازی‌شده در توسعه‌های C-API در نظر گرفته شده‌اند. به‌طور کلی نباید از آن‌ها با نوع‌های توکار مانند list و dict استفاده کرد، زیرا C-APIهای عمومی آن‌ها از پیش به‌طور داخلی از بخش‌های بحرانی استفاده می‌کنند؛ با این استثنای قابل توجه که PyDict_Next() برای آن باید بخش بحرانی از بیرون به دست گرفته شود.

بخش‌های بحرانی با تعلیق ضمنی بخش‌های بحرانی فعال از بن‌بست اجتناب می‌کنند؛ از این رو، آن‌ها دسترسی انحصاری‌ای از نوع آنچه قفل‌های سنتی مانند PyMutex فراهم می‌کنند را ارائه نمی‌دهند. هنگامی که یک بخش بحرانی آغاز می‌شود، قفل هر شیء برای آن شیء به دست گرفته می‌شود. اگر کدی که درون بخش بحرانی اجرا می‌شود توابع C-API را فراخوانی کند، می‌تواند بخش بحرانی را معلق کند و بدین ترتیب قفل هر شیء را آزاد کند تا نخ‌های دیگر بتوانند قفل هر شیء را برای همان شیء به دست آورند.

گونه‌هایی نیز موجود هستند که به‌جای اشیاء پایتون، اشاره‌گرهای PyMutex را می‌پذیرند. از این گونه‌ها برای آغاز یک بخش بحرانی در موقعیتی که PyObject وجود ندارد استفاده کنید -- برای مثال، هنگام کار با یک نوع C که PyObject را بسط نمی‌دهد یا دربر نمی‌گیرد، اما همچنان باید به شکلی که ممکن است به بن‌بست منجر شود، در API زبان C فراخوانی انجام دهد.

توابع و ساختارهایی که ماکروها از آن‌ها استفاده می‌کنند، برای مواردی که ماکروهای C موجود نیستند، در دسترس قرار گرفته‌اند. از آن‌ها باید فقط مطابق با بسط‌های داده‌شده‌ی ماکروها استفاده شود. توجه داشته باشید که اندازه و محتویات ساختارها ممکن است در نسخه‌های آینده پایتون تغییر کنند.

توجه

عملیاتی که نیاز به قفل کردن دو شیء به‌طور همزمان دارند، باید از Py_BEGIN_CRITICAL_SECTION2 استفاده کنند. شما نمی‌توانید از بخش‌های بحرانی تودرتو برای قفل کردن بیش از یک شیء به‌طور همزمان استفاده کنید، زیرا بخش بحرانی داخلی ممکن است بخش‌های بحرانی خارجی را تعلیق کند. این API راهی برای قفل کردن بیش از دو شیء به‌طور همزمان فراهم نمی‌کند.

مثال استفاده:

static PyObject *
set_field(MyObject *self, PyObject *value)
{
   Py_BEGIN_CRITICAL_SECTION(self);
   Py_SETREF(self->field, Py_XNewRef(value));
   Py_END_CRITICAL_SECTION();
   Py_RETURN_NONE;
}

در مثال بالا، Py_SETREF Py_DECREF را فراخوانی می‌کند که می‌تواند از طریق تابع آزادسازی یک شیء، کد دلخواهی را فراخوانی کند. API بخش بحرانی با اجازه دادن به ران‌تایم تا در صورتی که کد راه‌اندازی‌شده توسط نهایی‌ساز مسدود شود و PyEval_SaveThread() را فراخوانی کند، بخش بحرانی را به‌طور موقت معلق کند، از بن‌بست‌های احتمالی ناشی از بازورودپذیری و ترتیب قفل‌ها جلوگیری می‌کند.

Py_BEGIN_CRITICAL_SECTION(op)

قفل مخصوص هر شیء را برای شیء op به دست می‌آورد و یک بخش بحرانی را آغاز می‌کند.

در ساخت‌ نخ‌آزاد، این ماکرو به صورت زیر بسط می‌یابد:

{
    PyCriticalSection _py_cs;
    PyCriticalSection_Begin(&_py_cs, (PyObject*)(op))

در ساخت‌ پیش‌فرض، این ماکرو به { بسط داده می‌شود.

اضافه شده در نسخه‌ی 3.13.

Py_BEGIN_CRITICAL_SECTION_MUTEX(m)

قفل متقابل m را قفل می‌کند و یک بخش بحرانی را آغاز می‌کند.

در ساخت‌ نخ‌آزاد، این ماکرو به صورت زیر بسط می‌یابد:

{
     PyCriticalSection _py_cs;
     PyCriticalSection_BeginMutex(&_py_cs, m)

توجه داشته باشید که برخلاف Py_BEGIN_CRITICAL_SECTION، برای آرگومان این ماکرو هیچ قالب‌ریزی وجود ندارد؛ این آرگومان باید اشاره‌گری به PyMutex باشد.

در ساخت پیش‌فرض، این ماکرو به { بسط می‌یابد.

اضافه شده در نسخه‌ی 3.14.

Py_END_CRITICAL_SECTION()

بخش بحرانی را به پایان می‌رساند و قفل به ازای هر شیء را آزاد می‌کند.

در ساخت‌ نخ‌آزاد، این ماکرو به صورت زیر بسط می‌یابد:

    PyCriticalSection_End(&_py_cs);
}

در ساخت‌ پیش‌فرض، این ماکرو به } بسط می‌یابد.

اضافه شده در نسخه‌ی 3.13.

Py_BEGIN_CRITICAL_SECTION2(a, b)

قفل‌های به‌ازای هر شیء را برای اشیاء a و b به‌دست می‌گیرد و یک بخش بحرانی را آغاز می‌کند. قفل‌ها با ترتیبی ثابت (ابتدا کمترین نشانی) به‌دست گرفته می‌شوند تا از بن‌بست‌های ناشی از ترتیب قفل‌گیری جلوگیری شود.

در ساخت‌ نخ‌آزاد، این ماکرو به صورت زیر بسط می‌یابد:

{
    PyCriticalSection2 _py_cs2;
    PyCriticalSection2_Begin(&_py_cs2, (PyObject*)(a), (PyObject*)(b))

در ساخت‌ پیش‌فرض، این ماکرو به { بسط داده می‌شود.

اضافه شده در نسخه‌ی 3.13.

Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2)

قفل‌های متقابل m1 و m2 را قفل می‌کند و یک بخش بحرانی را آغاز می‌کند.

در ساخت‌ نخ‌آزاد، این ماکرو به صورت زیر بسط می‌یابد:

{
     PyCriticalSection2 _py_cs2;
     PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)

توجه داشته باشید که برخلاف Py_BEGIN_CRITICAL_SECTION2، هیچ تبدیل نوعی برای آرگومان‌های ماکرو انجام نمی‌شود — این آرگومان‌ها باید اشاره‌گرهای PyMutex باشند.

در ساخت پیش‌فرض، این ماکرو به { بسط می‌یابد.

اضافه شده در نسخه‌ی 3.14.

Py_END_CRITICAL_SECTION2()

بخش بحرانی را به پایان می‌رساند و قفل‌های هر شیء را آزاد می‌کند.

در ساخت‌ نخ‌آزاد، این ماکرو به صورت زیر بسط می‌یابد:

    PyCriticalSection2_End(&_py_cs2);
}

در ساخت‌ پیش‌فرض، این ماکرو به } بسط می‌یابد.

اضافه شده در نسخه‌ی 3.13.

API‌های قدیمی قفل‌گذاری

این API‌ها با معرفی PyMutex در پایتون 3.13 منسوخ شده‌اند.

type PyThread_type_lock

اشاره‌گری به قفل متقابل.

type PyLockStatus

نتیجه‌ی به‌دست آوردن یک قفل با مهلت زمانی.

enumerator PY_LOCK_FAILURE

به‌دست آوردن قفل ناموفق بود.

enumerator PY_LOCK_ACQUIRED

قفل با موفقیت به دست آمد.

enumerator PY_LOCK_INTR

قفل توسط یک سیگنال مختل شد.

PyThread_type_lock PyThread_allocate_lock(void)
قسمتی از ABI پایدار.

یک قفل جدید تخصیص می‌دهد.

در صورت موفقیت، این تابع یک قفل برمی‌گرداند؛ در صورت شکست، این تابع 0 را بدون تنظیم استثنا برمی‌گرداند.

فراخواننده نیازی به در اختیار داشتن وضعیت نخ متصل ندارد.

void PyThread_free_lock(PyThread_type_lock lock)
قسمتی از ABI پایدار.

قفل lock را از بین می‌برد. هنگام فراخوانی این تابع، قفل نباید در اختیار هیچ نخی باشد.

فراخواننده نیازی به در اختیار داشتن وضعیت نخ متصل ندارد.

PyLockStatus PyThread_acquire_lock_timed(PyThread_type_lock lock, long long microseconds, int intr_flag)
قسمتی از ABI پایدار.

به‌دست آوردن lock با مهلت زمانی.

این تابع برای به‌دست آوردن قفل به مدت microseconds میکروثانیه منتظر می‌ماند. اگر مهلت زمانی به پایان برسد، این تابع PY_LOCK_FAILURE را برمی‌گرداند. اگر microseconds برابر -1 باشد، این تابع به‌طور نامحدود تا زمانی که قفل آزاد شود منتظر می‌ماند.

اگر intr_flag برابر 1 باشد، ممکن است به‌دست‌آوردن قفل توسط یک سیگنال مختل شود که در این صورت این تابع PY_LOCK_INTR را برمی‌گرداند. پس از وقوع اختلال، معمولاً انتظار می‌رود که فراخواننده Py_MakePendingCalls() را فراخوانی کند تا استثنی را به کد پایتون منتشر کند.

اگر قفل با موفقیت به دست آید، این تابع PY_LOCK_ACQUIRED را برمی‌گرداند.

فراخواننده نیازی به در اختیار داشتن وضعیت نخ متصل ندارد.

int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
قسمتی از ABI پایدار.

lock را به دست بگیرید.

اگر waitflag برابر 1 باشد و در حال حاضر نخ دیگری قفل را در اختیار داشته باشد، این تابع تا زمانی که بتوان قفل را به دست آورد منتظر می‌ماند و همیشه 1 را برمی‌گرداند.

اگر waitflag برابر 0 باشد و نخ دیگری قفل را در اختیار داشته باشد، این تابع منتظر نمی‌ماند و در عوض 0 را برمی‌گرداند. اگر هیچ نخ دیگری قفل را در اختیار نداشته باشد، این تابع آن را به دست می‌آورد و 1 را برمی‌گرداند.

برخلاف PyThread_acquire_lock_timed()، به‌دست‌آوردن قفل نمی‌تواند توسط سیگنال مختل شود.

فراخواننده نیازی به در اختیار داشتن وضعیت نخ متصل ندارد.

int PyThread_release_lock(PyThread_type_lock lock)
قسمتی از ABI پایدار.

lock را آزاد می‌کند. اگر lock نگه داشته نشده باشد، این تابع خطای مهلک صادر می‌کند.

فراخواننده نیازی به در اختیار داشتن وضعیت نخ متصل ندارد.