线程状态和全局解释器锁¶
除非使用 CPython 的 free-threaded build 版本,否则Python 解释器通常都不是线程安全的。 为了支持多线程的 Python 程序,解释器有一个全局锁,称为 global interpreter lock 或 GIL,线程必须在访问 Python 对象之前先持有它。 如果没有这个锁,即使最简单的操作在多线程的程序中也可能造成问题:例如,当两个线程同时递增同一个对象的引用计数时,引用计数可能只增加一次而不是两次。
因此,线程必须持有 GIL 才能操作 Python 对象或唤起 Python 的 C API。
为了模拟并发,解释器会定期在字节指令间尝试切换线程 (参见 sys.setswitchinterval())。 因此在纯 Python 代码中也必须要有这个锁。
此外,全局解释器锁会在阻塞型 I/O 操作时被释放,如读取或写入文件等。 在 C API 中,这是通过 分离线程状态 完成的。
Python 解释器会将线程的局部信息保存在名为 PyThreadState 的数据结构中,或称 thread state。 每个线程都有一个指向 PyThreadState 的线程局部指针;这个指针所引用的线程状态会被视为 已附加线程状态。
一个线程在同一时刻只能拥有一个 attached thread state。 已附加线程状态通常等同于持有 GIL,但在自由线程构建中例外。 在启用 GIL 的构建中,附加线程状态会阻塞执行直到 GIL 可以被获取。 不过,即使在禁用 GIL 的构建中,仍然需要拥有已附加线程状态,因为解释器需要记住有哪些线程可以访问 Python 对象。
备注
即使在自由线程构建中,附加线程状态也可能发生阻塞,因为 GIL 可以被重新启用或者线程可能被临时挂起(例如在垃圾回收期间)。
通常,在使用 Python 的 C API 时总是会存在一个已附加线程状态,包括执行嵌入期间和实现方法时,因此你很少会需要自行设置线程状态。 仅在某些特定情况下,例如处于某个 Py_BEGIN_ALLOW_THREADS 代码块或全新线程中,线程才没有已附加线程状态。 如果不确定,可检查 PyThreadState_GetUnchecked() 是否返回 NULL。
如果最终发现确实需要创建线程状态,建议使用 PyThreadState_Ensure() 或 PyThreadState_EnsureFromView();它们会为您管理线程状态。
从扩展代码分离线程状态¶
大多数操作 thread state 的扩展代码具有以下简单结构:
Save the thread state in a local variable.
... Do some blocking I/O operation ...
Restore the thread state from the local variable.
这是如此常用因此增加了一对宏来简化它:
Py_BEGIN_ALLOW_THREADS
... 执行某些阻塞式的 I/O 操作 ...
Py_END_ALLOW_THREADS
Py_BEGIN_ALLOW_THREADS 宏将打开一个新块并声明一个隐藏的局部变量;Py_END_ALLOW_THREADS 宏将关闭这个块。
上面的代码块可扩展为下面的代码:
PyThreadState *_save;
_save = PyEval_SaveThread();
... 执行某些阻塞式的 I/O 操作 ...
PyEval_RestoreThread(_save);
下面介绍这些函数是如何运作的:
附加线程状态意味着为解释器持有 GIL。 要将其分离,则调用 PyEval_SaveThread() 并将结果保存在局部变量中。
通过分离线程状态,GIL 将被释放,以允许其他线程附加到该解释器并执行而让当前线程执行阻塞式 I/O。 当 I/O 操作完成时,可通过调用 PyEval_RestoreThread() 重新附加旧线程状态,该函数将等待至 GIL 可被获取。
备注
执行阻塞型 I/O 操作是分离线程状态最常见的使用场景,但它也适用于不需要访问 Python 对象或 Python 的 C API 的长时间运行的原生代码。 例如,标准库 zlib 和 hashlib 模块会在对数据执行压缩或哈希运算时分离 线程状态。
在 free-threaded build 中,通常不需要考虑 GIL,但是 分离线程状态仍然是需要的,因为解释器需要定期地阻塞所有线程以获取稳定的 Python 对象视图而不会产生竞争条件风险。 例如,CPython 目前会在运行垃圾回收器的时候暂时挂起所有线程。
警告
在解释器最终化期间分离线程状态可能导致非预期的行为。 请参阅 有关解释器最终化的注意事项 了解详情。
API¶
以下的宏被使用时通常不带末尾分号;请在 Python 源代码发布包中查看示例用法。
备注
这些宏对于在 free-threaded build 上防止死锁仍然是必要的。
-
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宏。
从外部线程使用 C API¶
当使用专门的 Python API(如 threading 模块)创建线程时,会自动为其关联一个附加线程状态,不过,当线程是从原生代码创建时(例如,由具有自己的线程管理的第三方库创建),它就不会持有附加线程状态。
如果你需要从这些线程调用 Python 代码(这常常会是前述第三方库提供的回调 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 并非免费操作,在 自由线程构建 上成本更高。一个多次调用解释器的外部线程——例如原生线程池中的工作线程——应避免在每次进入时都创建新的线程状态,并在每次退出时将其销毁。相反,应在线程启动时(或在其首次调用 Python 时以惰性方式)建立一个线程状态,在每次调用前后将其附加和分离,并在线程退出时将其彻底销毁一次。
使用 PyThreadState_New() 显式管理线程状态,并通过 PyEval_RestoreThread() 和 PyEval_SaveThread() 实现线程的附加与解附。这一过程会在线程生命周期的不同阶段,分三个独立阶段完成。
当线程启动时,为其创建一个线程状态。interp 是目标解释器;该解释器由创建此线程的代码捕获,而该代码在持有附加线程状态时执行了此操作(例如通过 PyInterpreterState_Get()):
PyThreadState *tstate = PyThreadState_New(interp);
随后,在每次调用 Python 时——这种情况在线程生命周期内可能会多次发生——将线程状态附加到当前调用中;执行需要该线程状态的 Python C API 调用;然后再次解绑线程,这样当线程处于非 Python 工作状态时就不会持有 GIL:
PyEval_RestoreThread(tstate);
result = CallSomeFunction(); /* your Python C API calls go here */
PyEval_SaveThread();
当线程完成向 Python 的调用后,请一次性销毁该线程状态:
PyEval_RestoreThread(tstate);
PyThreadState_Clear(tstate);
PyThreadState_DeleteCurrent();
用于从外部线程调用函数的通用入口点——PyThreadState_Ensure() 和较早的 PyGILState_Ensure()——并不 保证线程状态具有持久性:它们的线程状态生命周期被刻意定义为由具体实现决定,因此每次匹配的 acquire/release 对都可能创建并销毁一个线程状态。当您特别希望在不同调用之间复用同一个线程状态时,请使用 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()。不过,请不要仅因线程状态的生命周期可能在不同版本之间不一致而避免使用此函数;对该函数的修改将谨慎进行,并采用向后兼容的方式实施。特别是,线程局部变量及类似状态的保存将在不同 Python 版本之间保持一致。关于此函数是否会创建新的线程状态的具体行为,将在下文中说明;但请注意,这一行为未来可能会发生变化。
首先,该函数会检查是否存在已关联的线程状态。若存在,则进一步检查该线程状态的解释器是否与由 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()调用。
GIL 状态 API¶
以下 API 通常与子解释器不兼容,会在解释器最终化过程中导致进程卡死(参见 有关解释器最终化的注意事项)。因此,在 Python 3.15 中,这些 API 被 软弃用 取代,以支持 new APIs。
-
type PyGILState_STATE¶
- 属于 稳定 ABI.
由
PyGILState_Ensure()返回并传递给PyGILState_Release()的值的类型。-
enumerator PyGILState_LOCKED¶
当调用
PyGILState_Ensure()时 GIL 已经被持有。
-
enumerator PyGILState_UNLOCKED¶
当调用
PyGILState_Ensure()时 GIL 尚未被持有。
-
enumerator PyGILState_LOCKED¶
-
PyGILState_STATE PyGILState_Ensure()¶
- 属于 稳定 ABI.
确保当前线程可以调用 Python C API,无论 Python 的当前状态或 attached thread state 如何。只要每个调用都与对
PyGILState_Release()的调用相匹配,线程就可以根据需要多次调用此函数。通常,只要线程状态在调用 Release() 之前恢复到其先前状态,就可以在PyGILState_Ensure()和PyGILState_Release()调用之间使用其他与线程相关的 API。例如,可以正常使用Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS宏。返回值是一个不透明的"句柄",指向调用
PyGILState_Ensure()时的 attached thread state,必须将其传递给PyGILState_Release()以确保 Python 恢复到相同状态。尽管允许递归调用,但这些句柄 不能 共享 — 每次对PyGILState_Ensure()的独立调用都必须保存其对应的句柄,用于后续调用PyGILState_Release()。当该函数返回时,将存在一个 已附加的线程状态,线程将能够调用任意 Python 代码。
此函数无法返回错误。因此,错误要么是致命的(即会发送
SIGABRT并导致进程崩溃;参见Py_FatalError()),要么会导致线程被永久阻塞(例如在解释器最终化过程中)。警告
在解释器完成最终处理时调用此函数会导致线程无限挂起,从而可能引发死锁。更多详情请参阅 有关解释器最终化的注意事项。
此外,当从外部线程调用此函数时,该函数通常无法与子解释器协同工作,因为该函数无法获知是哪个解释器创建了该线程(因此会默认选择主解释器)。
在 3.14 版本发生变更: 如果在解释器处于终结阶段时调用此函数,当前线程将被挂起而非终止。
自 3.15 版起已处于 Soft deprecated 状态: 请使用
PyThreadState_Ensure()或PyThreadState_EnsureFromView()代替。
-
void PyGILState_Release(PyGILState_STATE)¶
- 属于 稳定 ABI.
释放先前获取的所有资源。本次调用结束后,Python 的状态将与对应
PyGILState_Ensure()调用之前相同(但通常调用者无法获知这一状态,因此采用了 GIL-state API)。对
PyGILState_Ensure()的每次调用都必须与在同一线程上对PyGILState_Release()的调用相匹配。自 3.15 版起已处于 Soft deprecated 状态: 请使用
PyThreadState_Release()代替。
-
PyThreadState *PyGILState_GetThisThreadState()¶
- 属于 稳定 ABI.
获取本线程最近一次处于 attached 状态的 线程状态。(如果最近一次线程状态已被删除,则返回
NULL。)如果调用者拥有关联的线程状态,则将其返回。
换言之,该函数会返回将用于
PyGILState_Ensure()的线程状态。如果该函数返回NULL,则PyGILState_Ensure将创建一个新的线程状态。此函数不会发生错误。
自 3.15 版起已处于 Soft deprecated 状态: 请使用
PyThreadState_Get()或PyThreadState_GetUnchecked()代替。
-
int PyGILState_Check()¶
如果当前线程中存在与
PyGILState_GetThisThreadState()返回的线程状态相匹配的 已附加的线程状态,则返回1;如果调用者未附加任何线程状态,或线程状态不匹配,则返回0。如果当前 Python 进程曾创建过子解释器,则此函数将*始终*返回
1。这主要是一种辅助/诊断功能。
在 3.4 版本加入.
自 3.15 版起已处于 Soft deprecated 状态: Use
PyThreadState_GetUnchecked() != NULLinstead.
有关 fork() 的注意事项¶
有关线程的另一个需要注意的重要问题是它们在面对 C fork() 调用时的行为。在大多数支持 fork() 的系统中,当一个进程执行 fork 之后将只有发出 fork 的线程存在。这对需要如何处理锁以及 CPython 的运行时内所有的存储状态都会有实质性的影响。
只保留“当前”线程这一事实意味着任何由其他线程所持有的锁永远不会被释放。Python 通过在 fork 之前获取内部使用的锁,并随后释放它们的方式为 os.fork() 解决了这个问题。此外,它还会重置子进程中的任何 Lock 对象。在扩展或嵌入 Python 时,没有办法通知 Python 在 fork 之前或之后需要获取或重置的附加(非 Python)锁。需要使用 OS 工具例如 pthread_atfork() 来完成同样的事情。此外,在扩展或嵌入 Python 时,直接调用 fork() 而不是通过 os.fork() (并返回到或调用至 Python 中) 调用可能会导致某个被 fork 之后失效的线程所持有的 Python 内部锁发生死锁。 PyOS_AfterFork_Child() 会尝试重置必要的锁,但并不总是能够做到。
所有其他线程都将结束这一事实也意味着 CPython 的运行时状态必须妥善清理,os.fork() 就是这样做的。 这意味着最终化归属于当前解释器的所有其他 PyThreadState 对象以及所有其他 PyInterpreterState 对象。由于这一点以及 "main" 解释器 的特殊性质,fork() 应当只在该解释器 的 "main" 线程中被调用,而 CPython 全局运行时最初就是在该线程中初始化的。只有当 exec() 将随后立即被调用的情况是唯一的例外。
高层级 API¶
这些是在编写多线程 C 扩展时最常用的类型和函数。
-
type PyThreadState¶
- 属于 稳定 ABI (作为不透明的结构体).
该数据结构代表单个线程的状态。唯一的公有数据成员为:
-
PyInterpreterState *interp¶
该线程的解释器状态。
-
PyInterpreterState *interp¶
-
void PyEval_InitThreads()¶
- 属于 稳定 ABI.
不执行任何操作的已弃用函数。
在 Python 3.6 及更老的版本中,此函数会在 GIL 不存在时创建它。
在 3.9 版本发生变更: 此函数现在不执行任何操作。
在 3.7 版本发生变更: 该函数现在由
Py_Initialize()调用,因此你无需再自行调用它。在 3.2 版本发生变更: 此函数已不再被允许在
Py_Initialize()之前调用。自 3.9 版本弃用.
-
PyThreadState *PyEval_SaveThread()¶
- 属于 稳定 ABI.
分离当前线程的 attached thread state 并返回该状态对象。调用此函数返回后,当前线程将不再关联任何 thread state 线程状态。
-
void PyEval_RestoreThread(PyThreadState *tstate)¶
- 属于 稳定 ABI.
将 attached thread state 设置为 tstate。传入的 thread state 不应 处于 已附加 状态,否则会导致死锁。调用此函数返回后,tstate 将被附加到当前线程。
备注
当运行时处于终结阶段时,若从某个线程调用此函数,该线程将被挂起直至程序退出,即便是由非 Python 创建的线程也不例外。更多详情请参考 有关解释器最终化的注意事项 小节。
在 3.14 版本发生变更: 如果在解释器处于终结阶段时调用此函数,当前线程将被挂起而非终止。
-
PyThreadState *PyThreadState_Get()¶
- 属于 稳定 ABI.
返回当前线程的 attached thread state。如果线程没有已附加的线程状态(例如,当处于
Py_BEGIN_ALLOW_THREADS代码块内部时),则会触发致命错误 (因此调用者无需检查返回值是否为NULL值)。
-
PyThreadState *PyThreadState_GetUnchecked()¶
与
PyThreadState_Get()类似,但如果其为 NULL 则不会杀死进程并设置致命错误。调用方要负责检查结果是否为 NULL 值。在 3.13 版本加入: 在 Python 3.5 到 3.12 中,此函数是私有的并且命名为
_PyThreadState_UncheckedGet()。
-
PyThreadState *PyThreadState_Swap(PyThreadState *tstate)¶
- 属于 稳定 ABI.
将 attached thread state 设置为 tstate,并返回调用此函数前已附加的 thread state 线程状态。
此函数在没有 attached thread state 的情况下调用也是安全的;此时它会直接返回
NULL,表示之前不存在线程状态。备注
与
PyGILState_Ensure()类似,当运行时处于终结阶段时,调用此函数会导致线程挂起。
低层级 API¶
-
PyThreadState *PyThreadState_New(PyInterpreterState *interp)¶
- 属于 稳定 ABI.
创建一个属于指定解释器对象的新线程状态对象。此操作不需要存在 attached thread state。
-
void PyThreadState_Clear(PyThreadState *tstate)¶
- 属于 稳定 ABI.
重置 thread state 对象中的所有信息。 tstate 必须处于 已附加 状态。
在 3.9 版本发生变更: 此函数现在会调用
PyThreadState.on_delete回调。在之前版本中,此操作是发生在PyThreadState_Delete()中的。在 3.13 版本发生变更:
PyThreadState.on_delete回调已被移除。
-
void PyThreadState_Delete(PyThreadState *tstate)¶
- 属于 稳定 ABI.
销毁一个 thread state 对象。tstate 不应被 已附加 到任何线程。tstate 必须在之前通过调用
PyThreadState_Clear()进行过重置。
-
void PyThreadState_DeleteCurrent(void)¶
分离 attached thread state (该状态必须已通过先前调用
PyThreadState_Clear()进行重置),然后销毁它。返回时将不会有任何 thread state 处于 已附加 状态。
-
PyFrameObject *PyThreadState_GetFrame(PyThreadState *tstate)¶
- 属于 稳定 ABI 自 3.10 版起.
获取 Python 线程状态 tstate 的当前帧。
返回一个 strong reference。如果没有当前执行的帧则返回
NULL。另请参阅
PyEval_GetFrame()。tstate 不得为
NULL,并且必须处于 已附加 状态。在 3.9 版本加入.
-
uint64_t PyThreadState_GetID(PyThreadState *tstate)¶
- 属于 稳定 ABI 自 3.10 版起.
获取 Python 线程状态 tstate 的唯一 thread state 标识符。
tstate 不得为
NULL,并且必须处于 已附加 状态。在 3.9 版本加入.
-
PyInterpreterState *PyThreadState_GetInterpreter(PyThreadState *tstate)¶
- 属于 稳定 ABI 自 3.10 版起.
获取 Python 线程状态 tstate 对应的解释器。
tstate 不得为
NULL,并且必须处于 已附加 状态。在 3.9 版本加入.
-
void PyThreadState_EnterTracing(PyThreadState *tstate)¶
暂停 Python 线程状态 tstate 中的追踪和性能分析。
使用
PyThreadState_LeaveTracing()函数来恢复它们。在 3.11 版本加入.
-
void PyThreadState_LeaveTracing(PyThreadState *tstate)¶
恢复 Python 线程状态 tstate 中被
PyThreadState_EnterTracing()函数暂停的追踪和性能分析。另请参阅
PyEval_SetTrace()和PyEval_SetProfile()函数。在 3.11 版本加入.
-
int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size)¶
- 这是 不稳定 API。它可能在次要版本中不经警告地被更改。
设置一个 Python 线程状态的栈保护起始地址和栈保护大小。
成功时,返回
0。失败时,设置一个异常并返回-1。CPython 通过在注意到机器的执行栈即将溢出时引发
RecursionError实现针对 C 代码的 递归控制。请查看Py_EnterRecursiveCall()函数的示例。为此,需要知道当前线程栈的位置,这通常是从操作系统获取的。例如当使用 Boost 库的boost::context之类的上下文切换技术使栈发生改变时,你必须调用PyUnstable_ThreadState_SetStackProtection()将变化通知给 CPython。在修改栈之前或之后调用
PyUnstable_ThreadState_SetStackProtection()。 不要在调用和栈修改之间调用任何其他 Python C API。请参阅
PyUnstable_ThreadState_ResetStackProtection()了解如何撤销此操作。在 3.15 版本加入.
-
void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)¶
- 这是 不稳定 API。它可能在次要版本中不经警告地被更改。
将一个 Python 线程状态的栈保护起始地址和栈保护大小重置为操作系统的默认值。
请参阅
PyUnstable_ThreadState_SetStackProtection()查看相关说明。在 3.15 版本加入.
-
PyObject *PyThreadState_GetDict()¶
- 返回值:借入的引用。 属于 稳定 ABI.
返回一个字典,扩展模块可在其中存储线程特定的状态信息。每个扩展模块应使用唯一的键来在此字典中存储状态。当没有 thread state 处于 已附加 状态时,调用此函数是安全的。如果此函数返回
NULL,则表示没有引发异常,调用者应假定没有线程状态被附加。
-
void PyEval_AcquireThread(PyThreadState *tstate)¶
- 属于 稳定 ABI.
将 tstate 附加 到当前线程,当前线程不能为
NULL或者已经 附加线程状态。调用方线程不能已经具有 attached thread state。
备注
当运行时处于终结阶段时,若从某个线程调用此函数,该线程将被挂起直至程序退出,即便是由非 Python 创建的线程也不例外。更多详情请参考 有关解释器最终化的注意事项 小节。
在 3.8 版本发生变更: 已被更新为与
PyEval_RestoreThread(),Py_END_ALLOW_THREADS()和PyGILState_Ensure()保持一致,如果在解释器正在最终化时被调用则会终结当前线程。在 3.14 版本发生变更: 如果在解释器处于终结阶段时调用此函数,当前线程将被挂起而非终止。
PyEval_RestoreThread()是一个始终可用的(即使线程尚未初始化)更高层级函数。
-
void PyEval_ReleaseThread(PyThreadState *tstate)¶
- 属于 稳定 ABI.
分离 attached thread state。 tstate 参数必须不为
NULL,该参数仅被用于检查它是否代表 attached thread state --- 如果不是,则会报告一个致命级错误。PyEval_SaveThread()是一个始终可用的(即使线程尚未初始化)更高层级函数。
异步通知¶
提供了一种向主解释器线程发送异步通知的机制。这些通知将采用函数指针和空指针参数的形式。
-
int Py_AddPendingCall(int (*func)(void*), void *arg)¶
- 属于 稳定 ABI.
将一个函数加入从主解释器线程调用的计划任务。成功时,将返回
0并将 func 加入要被主线程调用的等待队列。失败时,将返回-1但不会设置任何异常。当成功加入队列后,func 将 最终 附带参数 arg 被主解释器线程调用。对于正常运行的 Python 代码来说它将被异步地调用,但要同时满足以下两个条件:
位于 bytecode 的边界上;
主线程持有一个 attached thread state (因此 func 可以使用完整的 C API)。
func 必须在成功时返回
0,或在失败时返回-1并设置一个异常。 func 不会被中断来递归地执行另一个异步通知,但如果 thread state 已被分离则它仍可被中断以切换线程。此函数不需要 attached thread state。不过,要在子解释器中调用此函数,调用方必须具有 attached thread state。否则,函数 func 可能会被安排给错误的解释器来调用。
警告
这是一个底层函数,仅适用于非常特殊的场景。无法保证 func 会被尽可能快速地调用;如果主线程正忙于执行系统调用,则 func 不会在系统调用返回之前被调用。该函数通常**不**适合用于从任意 C 线程调用 Python 代码。相反,请使用
PyThreadState_EnsureFromView()。在 3.1 版本加入.
在 3.9 版本发生变更: 如果此函数在子解释器中被调用,则函数 func 将被安排在子解释器中调用,而不是在主解释器中调用。现在每个子解释器都有自己的计划调用列表。
在 3.12 版本发生变更: 此函数现在总是会安排 func 在主解释器中运行。
-
int Py_MakePendingCalls(void)¶
- 属于 稳定 ABI.
执行所有待命的调用。这通常会由解释器自动执行。
此函数成功时返回
0,失败时返回-1并设置一个异常。如果此函数不是在主解释器的主线程中被调用,它将不做任何事并返回
0。调用方必须持有 attached thread state 线程状态。在 3.1 版本加入.
在 3.12 版本发生变更: 此函数将只在主解释器中运行待命的调用。
-
int PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)¶
- 属于 稳定 ABI.
在某个线程中异步触发一个异常。如果该线程此前已调度过异常,则当前调度内容将被覆盖。
id 参数是目标线程的线程 ID,由
PyThread_get_thread_ident()返回。exc 是将被抛出的异常类型;若需清除待处理异常(如有),则使用NULL。返回受影响的线程状态数量。如果找到 id,则通常返回
1,即使未进行任何更改(给定的 exc 已经处于待处理状态,或者 exc 为NULL但尚未有异常处于待处理状态)。如果未找到线程 ID,则返回0。此操作不会引发任何异常。为防止被无意误用,您必须编写自己的 C 语言扩展函数来调用此函数。调用此函数时必须传入一个 已附加的线程状态。此函数不会 窃取 任何指向 exc 的引用。此函数并不一定会中断诸如
sleep()之类的系统调用。在 3.7 版本发生变更: id 形参的类型已从 long 变为 unsigned long。
操作系统线程 API¶
-
PYTHREAD_INVALID_THREAD_ID¶
代表无效线程 ID 的哨兵值。
该值目前等于
(unsigned long)-1。
-
unsigned long PyThread_start_new_thread(void (*func)(void*), void *arg)¶
- 属于 稳定 ABI.
在新线程中启动函数 func 并传入参数 arg。结果线程将不被合并。
func 不可为
NULL,但 arg 可以为NULL。成功时,此函数将返回新线程的标识号;失败时,将返回
PYTHREAD_INVALID_THREAD_ID。调用方不需要持有 attached thread state。
-
unsigned long PyThread_get_thread_ident(void)¶
- 属于 稳定 ABI.
返回当前线程的标识号,它一定不为零。
此函数不会执行失败,并且调用方不需要持有 attached thread state。
参见
threading.get_ident()和threading.Thread.ident将此标识符暴露给 Python。
-
PyObject *PyThread_GetInfo(void)¶
- 属于 稳定 ABI 自 3.3 版起.
获取有关当前线程的 结构体序列 对象形式的基本信息。此信息可在 Python 中作为
sys.thread_info访问。成功时,此函数将返回一个指向线程信息的新的 strong reference;失败时,将返回
NULL并设置一个异常。调用方必须持有一个 attached thread state。
-
PY_HAVE_THREAD_NATIVE_ID¶
该宏将在系统支持原生线程 ID 时被定义。
-
unsigned long PyThread_get_thread_native_id(void)¶
- 属于 稳定 ABI on platforms with native thread IDs.
获取当前线程由操作系统的内核所分配的原生标识号,它绝对不会小于零。
此函数仅在定义了
PY_HAVE_THREAD_NATIVE_ID时可用。此函数不会执行失败,并且调用方不需要持有 attached thread state。
-
void PyThread_exit_thread(void)¶
- 属于 稳定 ABI.
终结当前线程。此函数通常被视为是不安全的并应避免使用。它只是为了向下兼容而被保留。
此函数仅在整个调用栈中的所有函数都被编写为能够安全地支持它时才能被安全地调用。
警告
如果当前系统使用 POSIX 线程(或称“p 线程”),此函数将调用 pthread_exit(3),它会尝试展开栈并在某些 libc 实现上调用 C++ 析构器,如果抵达一个
noexcept函数,它可能会终结进程。在其他系统,如 macOS 上,只执行展开。在 Windows 上,此函数将调用
_endthreadex(),它将杀掉线程而不调用 C++ 析构器。在任何情况下,都存在线程栈损坏的风险。
自 3.14 版本弃用.
-
int PyThread_set_stacksize(size_t size)¶
- 属于 稳定 ABI.
将当前线程的栈大小设为 size 个字节。
此函数成功时返回
0,如果 size 无效则返回-1,或者如果系统不支持修改栈大小则返回-2。此函数不会设置异常。调用方不需要持有 attached thread state。
-
size_t PyThread_get_stacksize(void)¶
- 属于 稳定 ABI.
返回以字节为单位的当前线程栈大小,或者如果是使用系统的默认栈大小则返回
0。调用方不需要持有 attached thread state。