同步原语

C-API 提供了一个基本的互斥锁。

type PyMutex

一个互斥锁。 PyMutex 应当被初始化为零以代表未加锁状态。例如:

PyMutex mutex = {0};

PyMutex 的实例不应被拷贝或移动。 PyMutex 的内容和地址都是有意义的,它必须在内存中保持一个固定的、可写的位置。

备注

PyMutex 目前占用一个字节,但这个大小应当被视为是不稳定的。这个大小可能在未来的 Python 发布版中发生改变而不会设置弃用期。

Added in version 3.13.

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

锁定互斥锁 m。如果另一个线程已经锁定了它,调用方线程将会阻塞直到互斥锁被解锁。在阻塞期间,如果线程存在 线程状态 则会临时释放它。

Added in version 3.13.

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

解锁互斥锁 m。该互斥锁必须已被锁定 --- 否则,此函数将发生致命错误。

Added in version 3.13.

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

若互斥锁 m 当前处于锁定状态,则返回非零值;否则返回零。

备注

此函数仅适用于断言和调试场景,请勿将其用于并发控制决策,因为锁状态可能在检查后立即发生变化。

Added in version 3.14.

Python 关键节 API

此关键节 API 为 自由线程 CPython 的每对象锁之上提供了一个死锁避免层。它们旨在替代对 global interpreter lock 的依赖,而在具有全局解释器锁的 Python 版本上将不做任何操作。

关键节被设计用于在 C-API 扩展中实现的自定义类型。它们通常不应当被用于内置类型如 listdict 因为它们的公有 C-API 已经在内部使用了关键节,一个显著的例外是 PyDict_Next(),它需要在外部获取关键节。

关键节是通过隐式地挂起活动关键节来避免死锁的,因此,它们并不提供传统锁如 PyMutex 所提供的那种独占访问。 当一个关键节启动时,将会获取对象的每对象锁。如果关键节内部执行的代码调用了 C-API 函数那么它可以挂起关键节从而释放这个每对象锁,这样其他线程就可以获取同一个对象的每对象锁。

此外,还提供了接受 PyMutex 指针(而非 Python 对象)的函数变体。当你处于没有 PyObject 的场景中时(例如,处理一个既未继承也未封装 PyObject 的 C 类型,但仍需以可能导致死锁的方式调用 C API),请使用这些变体来启动临界区。

宏所使用的函数和结构体是针对 C 宏不可用的场景而公开的。它们应当仅被用于给定的宏扩展中。请注意这些结构体的大小和内容在未来的 Python 版本中可能发生改变。

备注

需要同时锁定两个对象的操作必须使用 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,它可以通过一个对象的取消分配函数来调用任意代码。当由最终化器触发的代码发生阻塞并调用 PyEval_SaveThread() 时关键节 API 将通过允许运行临时挂起关键节来避免由于重入和锁顺序导致的潜在死锁。

Py_BEGIN_CRITICAL_SECTION(op)

为对象 op 获取每对象锁并开始一个关键节。

在自由线程构建版中,该宏将扩展为:

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

在默认构建版中,该宏将扩展为 {

Added in version 3.13.

Py_BEGIN_CRITICAL_SECTION_MUTEX(m)

锁定互斥锁 m 并开始一个临界区。

在自由线程构建版中,该宏将扩展为:

{
     PyCriticalSection _py_cs;
     PyCriticalSection_BeginMutex(&_py_cs, m)

需要注意的是,与 Py_BEGIN_CRITICAL_SECTION 不同,此宏的参数无需类型转换——它必须是一个 PyMutex 指针。

在默认构建版中,该宏将扩展为 {

Added in version 3.14.

Py_END_CRITICAL_SECTION()

结束关键节并释放每对象锁。

在自由线程构建版中,该宏将扩展为:

    PyCriticalSection_End(&_py_cs);
}

在默认构建版中,该宏将扩展为 }

Added in version 3.13.

Py_BEGIN_CRITICAL_SECTION2(a, b)

为对象 ab 获取每对象锁并开始一个关键节。 这些锁将按一致的顺序获取(最低的地址在最前)以避免锁排序死锁。

在自由线程构建版中,该宏将扩展为:

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

在默认构建版中,该宏将扩展为 {

Added in version 3.13.

Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2)

锁定互斥锁 m1m2 并开始一个临界区。

在自由线程构建版中,该宏将扩展为:

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

需要注意的是,与 Py_BEGIN_CRITICAL_SECTION2 不同,此宏的参数无需类型转换——它们必须是 PyMutex 指针。

在默认构建版中,该宏将扩展为 {

Added in version 3.14.

Py_END_CRITICAL_SECTION2()

结束关键节并释放每对象锁。

在自由线程构建版中,该宏将扩展为:

    PyCriticalSection2_End(&_py_cs2);
}

在默认构建版中,该宏将扩展为 }

Added in version 3.13.

旧式加锁 API

这些 API 自 Python 3.13 起已随 PyMutex 的引入而过时。

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 且不设置异常。

调用方不需要持有 attached thread state

void PyThread_free_lock(PyThread_type_lock lock)
属于 稳定 ABI.

销毁 lock。在调用此函数时该锁不应被任何线程持有。

调用方不需要持有 attached thread state

PyLockStatus PyThread_acquire_lock_timed(PyThread_type_lock lock, long long microseconds, int intr_flag)
属于 稳定 ABI.

获取 lock 并附带超时控制。

此函数将等待 microseconds 微秒以获取锁。如果达到超时限制,此函数将返回 PY_LOCK_FAILURE。如果 microseconds-1,它将无限等待直到锁被释放。

如果 intr_flag1,获取锁可能会被信号中断,在这种情况下此函数将返回 PY_LOCK_INTR。当被中断时,通常会预期调用方将执行对 Py_MakePendingCalls() 的调用以将一个异常传播给 Python 代码。

如果锁被成功获取,此函数将返回 PY_LOCK_ACQUIRED

调用方不需要持有 attached thread state

int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
属于 稳定 ABI.

获取 lock

如果 waitflag1 且另一个线程目前持有锁,此函数将等待直到锁可被获取并将始终返回 1

如果 waitflag0 且另一个线程持有锁,此函数将不会等待而是返回 0。如果锁未被另一个线程持有,则此函数将获取它并返回 1

不同于 PyThread_acquire_lock_timed(),获取锁不会被信号中断。

调用方不需要持有 attached thread state

int PyThread_release_lock(PyThread_type_lock lock)
属于 稳定 ABI.

释放 lock。如果 lock 未被持有,则此函数将引发致命错误。

调用方不需要持有 attached thread state