初始化、终结和线程 — Python 文档

来自菜鸟教程
Python/docs/3.9/c-api/init
跳转至:导航、​搜索

初始化、结束和线程

另见 Python 初始化配置

Python初始化之前

在嵌入 Python 的应用程序中,必须在使用任何其他 Python/C API 函数之前调用 Py_Initialize() 函数; 除了一些函数和 全局配置变量

在 Python 初始化之前可以安全地调用以下函数:

笔记

以下函数不应在Py_Initialize()之前调用Py_EncodeLocale(), Py_GetPath(), [X138X(X138X])Py 、Py_GetExecPrefix()Py_GetProgramFullPath()Py_GetPythonHome()、Py_Get2ProgramName()[Init_Py_Get2ThreadName)()[InitX8X] 。


全局配置变量

Python 具有用于全局配置的变量来控制不同的功能和选项。 默认情况下,这些标志由 命令行选项 控制。

通过选项设置标志时,标志的值是选项所设置的次数。 例如,-bPy_BytesWarningFlag 设置为 1,而 -bbPy_BytesWarningFlag 设置为 2。

int Py_BytesWarningFlag

比较 bytesbytearraystrbytesint 时发出警告。 如果大于或等于 2,则发出错误。

-b 选项设置。

int Py_DebugFlag

打开解析器调试输出(仅限专家,取决于编译选项)。

-d 选项和 PYTHONDEBUG 环境变量设置。

int Py_DontWriteBytecodeFlag

如果设置为非零,Python 将不会尝试在导入源模块时写入 .pyc 文件。

-B 选项和 PYTHONDONTWRITEBYTECODE 环境变量设置。

int Py_FrozenFlag

Py_GetPath() 中计算模块搜索路径时抑制错误消息。

_freeze_importlibfrozenmain 程序使用的私有标志。

int Py_HashRandomizationFlag

如果 PYTHONHASHSEED 环境变量设置为非空字符串,则设置为 1

如果标志不为零,则读取 PYTHONHASHSEED 环境变量以初始化秘密哈希种子。

int Py_IgnoreEnvironmentFlag

忽略所有 PYTHON* 环境变量,例如 PYTHONPATHPYTHONHOME,可以设置。

-E-I 选项设置。

int Py_InspectFlag

当脚本作为第一个参数传递或使用 -c 选项时,执行脚本或命令后进入交互模式,即使 sys.stdin 看起来不是一个终端。

-i 选项和 PYTHONINSPECT 环境变量设置。

int Py_InteractiveFlag
-i 选项设置。
int Py_IsolatedFlag

在隔离模式下运行 Python。 在隔离模式下 sys.path 既不包含脚本的目录,也不包含用户的站点包目录。

-I 选项设置。

3.4 版中的新功能。

int Py_LegacyWindowsFSEncodingFlag

如果标志非零,则使用 mbcs 编码而不是文件系统编码的 UTF-8 编码。

如果 PYTHONLEGACYWINDOWSFSENCODING 环境变量设置为非空字符串,则设置为 1

有关更多详细信息,请参阅 PEP 529

int Py_LegacyWindowsStdioFlag

如果标志非零,则使用 io.FileIO 而不是 WindowsConsoleIO 用于 sys 标准流。

如果 PYTHONLEGACYWINDOWSSTDIO 环境变量设置为非空字符串,则设置为 1

有关更多详细信息,请参阅 PEP 528

int Py_NoSiteFlag

禁用模块 site 的导入和它所需要的 sys.path 的站点相关操作。 如果稍后显式导入 site,也请禁用这些操作(如果您希望触发它们,请调用 site.main())。

-S 选项设置。

int Py_NoUserSiteDirectory

不要将 用户站点包目录 添加到 sys.path

-s-I 选项以及 PYTHONNOUSERSITE 环境变量设置。

int Py_OptimizeFlag
-O 选项和 PYTHONOPTIMIZE 环境变量设置。
int Py_QuietFlag

即使在交互模式下也不显示版权和版本消息。

-q 选项设置。

3.2 版中的新功能。

int Py_UnbufferedStdioFlag

强制 stdout 和 stderr 流无缓冲。

-u 选项和 PYTHONUNBUFFERED 环境变量设置。

int Py_VerboseFlag

每次初始化模块时打印一条消息,显示加载它的位置(文件名或内置模块)。 如果大于或等于 2,则为搜索模块时检查的每个文件打印一条消息。 还提供有关退出时模块清理的信息。

-v 选项和 PYTHONVERBOSE 环境变量设置。


初始化和完成解释器

void Py_Initialize()

初始化 Python 解释器。 在嵌入 Python 的应用程序中,应该在使用任何其他 Python/C API 函数之前调用它; 有关少数例外情况,请参阅 Before Python Initialization

这将初始化加载模块表 (sys.modules),并创建基本模块 builtins__main__sys。 它还初始化模块搜索路径 (sys.path)。 不设置sys.argv; 为此使用 PySys_SetArgvEx()。 这是第二次调用时的空操作(不首先调用 Py_FinalizeEx())。 没有返回值; 如果初始化失败,这是一个致命错误。

笔记

在 Windows 上,将控制台模式从 O_TEXT 更改为 O_BINARY,这也会影响使用 C 运行时的控制台的非 Python 使用。

void Py_InitializeEx(int initsigs)
如果 initsigs1,则此函数的工作方式类似于 Py_Initialize()。 如果 initsigs0,它会跳过信号处理程序的初始化注册,这在嵌入 Python 时可能很有用。
int Py_IsInitialized()
当 Python 解释器已初始化时返回真(非零),否则返回假(零)。 调用 Py_FinalizeEx() 后,返回 false,直到再次调用 Py_Initialize()
int Py_FinalizeEx()

撤消由 Py_Initialize() 进行的所有初始化以及 Python/C API 函数的后续使用,并销毁所有已创建但尚未销毁的子解释器(请参阅下面的 Py_NewInterpreter())自上次调用 Py_Initialize() 以来。 理想情况下,这会释放 Python 解释器分配的所有内存。 这是第二次调用时的空操作(无需先再次调用 Py_Initialize())。 通常返回值为0。 如果在终结过程中出现错误(刷新缓冲数据),则返回 -1

提供此功能有多种原因。 嵌入应用程序可能希望重新启动 Python,而不必重新启动应用程序本身。 从动态可加载库(或 DLL)加载 Python 解释器的应用程序可能希望在卸载 DLL 之前释放 Python 分配的所有内存。 在寻找应用程序内存泄漏的过程中,开发人员可能希望在退出应用程序之前释放 Python 分配的所有内存。

Bugs 和警告: 模块和模块中对象的销毁是按随机顺序进行的; 这可能会导致析构函数(__del__() 方法)在依赖于其他对象(甚至函数)或模块时失败。 不会卸载 Python 加载的动态加载的扩展模块。 Python解释器分配的少量内存可能无法释放(如果发现泄漏,请报告)。 对象之间的循环引用所占用的内存不会被释放。 一些由扩展模块分配的内存可能不会被释放。 如果多次调用初始化例程,某些扩展可能无法正常工作; 如果应用程序多次调用 Py_Initialize()Py_FinalizeEx(),就会发生这种情况。

3.6 版中的新功能。

void Py_Finalize()
这是 Py_FinalizeEx() 的向后兼容版本,忽略返回值。


工艺参数

int Py_SetStandardStreamEncoding(const char *encoding, const char *errors)

这个函数应该在 Py_Initialize() 之前调用,如果它被调用的话。 它指定使用标准 IO 的编码和错误处理,其含义与 str.encode() 中的含义相同。

它覆盖 PYTHONIOENCODING 值,并允许嵌入代码在环境变量不起作用时控制 IO 编码。

encoding 和/或 errors 可能是 NULL 以使用 PYTHONIOENCODING 和/或默认值(取决于其他设置)。

请注意,sys.stderr 始终使用“backslashreplace”错误处理程序,无论此(或任何其他)设置如何。

如果Py_FinalizeEx()被调用,则需要再次调用该函数,以影响后续对Py_Initialize()的调用。

如果成功,则返回 0,错误时返回非零值(例如 在解释器已经初始化之后调用)。

3.4 版中的新功能。

void Py_SetProgramName(const wchar_t *name)

这个函数应该在第一次调用 Py_Initialize() 之前调用,如果它被调用的话。 它告诉解释器程序的 main() 函数的 argv[0] 参数的值(转换为宽字符)。 Py_GetPath() 和下面的一些其他函数使用它来查找与解释器可执行文件相关的 Python 运行时库。 默认值为 'python'。 该参数应指向静态存储中以零结尾的宽字符串,其内容在程序执行期间不会更改。 Python 解释器中的任何代码都不会更改此存储的内容。

使用 Py_DecodeLocale() 解码字节字符串以获得 wchar_* 字符串。

wchar *Py_GetProgramName()
返回用 Py_SetProgramName() 设置的程序名称,或默认值。 返回的字符串指向静态存储; 调用者不应修改其值。
wchar_t *Py_GetPrefix()
为已安装的独立于平台的文件返回 前缀 。 这是通过一些复杂的规则从用 Py_SetProgramName() 和一些环境变量设置的程序名称得出的; 例如,如果程序名称为'/usr/local/bin/python',则前缀为'/usr/local'。 返回的字符串指向静态存储; 调用者不应修改其值。 这对应于顶层 Makefile 中的 prefix 变量和构建时 configure 脚本的 --prefix 参数。 该值可用于 Python 代码作为 sys.prefix。 它仅在 Unix 上有用。 另请参阅下一个函数。
wchar_t *Py_GetExecPrefix()

为已安装的平台 相关 文件返回 exec-prefix。 这是通过一些复杂的规则从用 Py_SetProgramName() 和一些环境变量设置的程序名称得出的; 例如,如果程序名称为'/usr/local/bin/python',则exec-prefix 为'/usr/local'。 返回的字符串指向静态存储; 调用者不应修改其值。 这对应于顶层 Makefile 中的 exec_prefix 变量和构建时 configure 脚本的 --exec-prefix 参数。 该值可用于 Python 代码作为 sys.exec_prefix。 它仅在 Unix 上有用。

背景:当平台相关文件(例如可执行文件和共享库)安装在不同的目录树中时,exec-prefix 与前缀不同。 在典型安装中,平台相关文件可能安装在 /usr/local/plat 子树中,而平台无关文件可能安装在 /usr/local 中。

一般来说,平台是硬件和软件家族的组合,例如 运行 Solaris 2.x 操作系统的 Sparc 机器被认为是同一平台,但运行 Solaris 2.x 的 Intel 机器是另一个平台,而运行 Linux 的 Intel 机器又是另一个平台。 同一操作系统的不同大版本通常也形成不同的平台。 非 Unix 操作系统则是另一回事。 这些系统上的安装策略是如此不同,以至于前缀和 exec-prefix 没有意义,并设置为空字符串。 请注意,编译后的 Python 字节码文件是平台无关的(但不独立于编译它们的 Python 版本!)。

系统管理员将知道如何配置 mountautomount 程序以在平台之间共享 /usr/local,同时让 /usr/local/plat 成为每个平台的不同文件系统。

wchar_t *Py_GetProgramFullPath()
返回 Python 可执行文件的完整程序名称; 这被计算为从程序名称(由上面的 Py_SetProgramName() 设置)派生默认模块搜索路径的副作用。 返回的字符串指向静态存储; 调用者不应修改其值。 该值可用于 Python 代码作为 sys.executable
wchar_t *Py_GetPath()
返回默认模块搜索路径; 这是根据程序名称(由上面的 Py_SetProgramName() 设置)和一些环境变量计算得出的。 返回的字符串由一系列由平台相关的定界符分隔的目录名称组成。 分隔符在 Unix 和 macOS 上是 ':',在 Windows 上是 ';'。 返回的字符串指向静态存储; 调用者不应修改其值。 列表 sys.path 在解释器启动时用这个值初始化; 它可以(通常是)稍后修改以更改加载模块的搜索路径。
void Py_SetPath(const wchar_t*)

设置默认模块搜索路径。 如果在 Py_Initialize() 之前调用此函数,则 Py_GetPath() 不会尝试计算默认搜索路径,而是使用提供的搜索路径。 如果 Python 是由完全了解所有模块位置的应用程序嵌入的,这将非常有用。 路径组件应由平台相关的定界符分隔,在 Unix 和 macOS 上为 ':',在 Windows 上为 ';'

这也会导致 sys.executable 设置为程序完整路径(请参阅 Py_GetProgramFullPath())以及 sys.prefixsys.exec_prefix 为空。 如果需要,调用者可以在调用 Py_Initialize() 后修改这些。

使用 Py_DecodeLocale() 解码字节字符串以获得 wchar_* 字符串。

路径参数在内部复制,因此调用者可以在调用完成后释放它。

3.8 版更改: 程序完整路径现在用于 sys.executable,而不是程序名称。

const char *Py_GetVersion()

返回此 Python 解释器的版本。 这是一个看起来像的字符串

"3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]"

第一个单词(直到第一个空格字符)是当前的 Python 版本; 前三个字符是用句点分隔的主要和次要版本。 返回的字符串指向静态存储; 调用者不应修改其值。 该值可用于 Python 代码作为 sys.version

const char *Py_GetPlatform()
返回当前平台的平台标识符。 在 Unix 上,这由操作系统的“官方”名称组成,转换为小写,后跟主要修订号; 例如,对于 Solaris 2.x,也称为 SunOS 5.x,该值为 'sunos5'。 在 macOS 上,它是 'darwin'。 在 Windows 上,它是 'win'。 返回的字符串指向静态存储; 调用者不应修改其值。 该值可用于 Python 代码作为 sys.platform
const char *Py_GetCopyright()

返回当前 Python 版本的官方版权字符串,例如

'Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam'

返回的字符串指向静态存储; 调用者不应修改其值。 该值可用于 Python 代码作为 sys.copyright

const char *Py_GetCompiler()

在方括号中返回用于构建当前 Python 版本的编译器的指示,例如:

"[GCC 2.7.2.2]"

返回的字符串指向静态存储; 调用者不应修改其值。 该值作为变量 sys.version 的一部分可用于 Python 代码。

const char *Py_GetBuildInfo()

返回有关当前 Python 解释器实例的序列号和构建日期和时间的信息,例如

"#67, Aug  1 1997, 22:34:28"

返回的字符串指向静态存储; 调用者不应修改其值。 该值作为变量 sys.version 的一部分可用于 Python 代码。

void PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath)

根据 argcargv 设置 sys.argv。 这些参数类似于传递给程序的 main() 函数的参数,不同之处在于第一个条目应该引用要执行的脚本文件,而不是托管 Python 解释器的可执行文件。 如果没有要运行的脚本,argv 中的第一个条目可以是空字符串。 如果此函数无法初始化 sys.argv,则会使用 Py_FatalError() 发出致命条件信号。

如果 updatepath 为零,这就是函数所做的全部。 如果 updatepath 非零,该函数还会根据以下算法修改 sys.path

  • 如果在 argv[0] 中传递现有脚本的名称,则脚本所在目录的绝对路径将被添加到 sys.path

  • 否则(即,如果 argc0argv[0] 未指向现有文件名),则在 sys.path 前附加一个空字符串,这与添加当前工作目录 (".") 相同。

使用 Py_DecodeLocale() 解码字节字符串以获得 wchar_* 字符串。

笔记

建议应用程序嵌入 Python 解释器用于执行单个脚本以外的目的,将 0 作为 updatepath 传递,并在需要时自行更新 sys.path。 参见 CVE-2008-5983

在 3.1.3 之前的版本中,您可以通过在调用 PySys_SetArgv() 后手动弹出第一个 sys.path 元素来实现相同的效果,例如使用:

PyRun_SimpleString("import sys; sys.path.pop(0)\n");

版本 3.1.3 中的新功能。

void PySys_SetArgv(int argc, wchar_t **argv)

此函数的工作方式类似于 PySys_SetArgvEx(),其中 updatepath 设置为 1,除非 python 解释器是用 -I 启动的]。

使用 Py_DecodeLocale() 解码字节字符串以获得 wchar_* 字符串。

3.4 版更改: updatepath 值取决于 -I

void Py_SetPythonHome(const wchar_t *home)

设置默认的“home”目录,即标准 Python 库的位置。 有关参数字符串的含义,请参阅 PYTHONHOME

该参数应指向静态存储中以零结尾的字符串,其内容在程序执行期间不会更改。 Python 解释器中的任何代码都不会更改此存储的内容。

使用 Py_DecodeLocale() 解码字节字符串以获得 wchar_* 字符串。

w_char *Py_GetPythonHome()
返回默认的“home”,即之前调用 Py_SetPythonHome() 设置的值,或者 PYTHONHOME 环境变量的值,如果它设置。


线程状态和全局解释器锁

Python 解释器不是完全线程安全的。 为了支持多线程 Python 程序,有一个全局锁,称为 全局解释器锁GIL,必须由当前线程持有才能安全地访问 Python 对象. 如果没有锁,即使是最简单的操作也可能导致多线程程序出现问题:例如,当两个线程同时增加同一个对象的引用计数时,引用计数可能最终只会增加一次而不是两次。

因此,存在只有获得GIL的线程才能操作Python对象或调用Python/C API函数的规则。 为了模拟执行的并发性,解释器会定期尝试切换线程(参见 sys.setswitchinterval())。 锁也会在潜在的阻塞 I/O 操作(如读取或写入文件)周围释放,以便其他 Python 线程可以同时运行。

Python 解释器将一些特定于线程的簿记信息保存在名为 PyThreadState 的数据结构中。 还有一个全局变量指向当前的 PyThreadState:它可以使用 PyThreadState_Get() 来检索。

从扩展代码中释放 GIL

大多数操作 GIL 的扩展代码具有以下简单结构:

Save the thread state in a local variable.
Release the global interpreter lock.
... Do some blocking I/O operation ...
Reacquire the global interpreter lock.
Restore the thread state from the local variable.

这很常见,以至于存在一对宏来简化它:

Py_BEGIN_ALLOW_THREADS
... Do some blocking I/O operation ...
Py_END_ALLOW_THREADS

Py_BEGIN_ALLOW_THREADS 宏打开一个新块并声明一个隐藏的局部变量; Py_END_ALLOW_THREADS 宏关闭块。

上面的块扩展为以下代码:

PyThreadState *_save;

_save = PyEval_SaveThread();
... Do some blocking I/O operation ...
PyEval_RestoreThread(_save);

以下是这些函数的工作原理:全局解释器锁用于保护指向当前线程状态的指针。 在释放锁和保存线程状态时,必须在释放锁之前检索当前线程状态指针(因为另一个线程可以立即获取锁并将自己的线程状态存储在全局变量中)。 反之,在获取锁和恢复线程状态时,必须在存储线程状态指针之前获取锁。

笔记

调用系统 I/O 函数是释放 GIL 的最常见用例,但它在调用不需要访问 Python 对象的长时间运行的计算之前也很有用,例如在内存缓冲区上运行的压缩或加密函数。 例如,标准的 zlibhashlib 模块在压缩或散列数据时释放 GIL。


非 Python 创建的线程

当使用专用 Python API(例如 threading 模块)创建线程时,线程状态会自动关联到它们,因此上面显示的代码是正确的。 但是,当线程是从 C 创建的(例如由具有自己线程管理的第三方库)时,它们不持有 GIL,也没有线程状态结构。

如果您需要从这些线程调用 Python 代码(通常这将是上述第三方库提供的回调 API 的一部分),您必须首先通过创建线程状态数据结构向解释器注册这些线程,然后获取GIL,最后存储它们的线程状态指针,然后才能开始使用 Python/C API。 完成后,应该重置线程状态指针,释放 GIL,最后释放线程状态数据结构。

PyGILState_Ensure()PyGILState_Release() 函数自动执行上述所有操作。 从 C 线程调用 Python 的典型习惯用法是:

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

/* Perform Python actions here. */
result = CallSomeFunction();
/* evaluate result or handle exception */

/* Release the thread. No Python API allowed beyond this point. */
PyGILState_Release(gstate);

请注意,PyGILState_* 函数假设只有一个全局解释器(由 Py_Initialize() 自动创建)。 Python 支持创建额外的解释器(使用 Py_NewInterpreter()),但不支持混合多个解释器和 PyGILState_* API。


关于 fork() 的注意事项

关于线程的另一个需要注意的重要事项是它们在面对 C fork() 调用时的行为。 在大多数具有 fork() 的系统上,在进程 fork 之后,只有发出 fork 的线程才会存在。 这对必须如何处理锁以及 CPython 运行时中的所有存储状态都有具体影响。

只有“当前”线程保留的事实意味着其他线程持有的任何锁将永远不会被释放。 Python 为 os.fork() 解决了这个问题,方法是在 fork 之前获取它内部使用的锁,然后释放它们。 此外,它会重置子进程中的任何 锁定对象 。 在扩展或嵌入 Python 时,无法通知 Python 需要在 fork 之前获取或在 fork 之后重置的其他(非 Python)锁。 需要使用诸如 pthread_atfork() 之类的操作系统工具来完成相同的事情。 此外,在扩展或嵌入 Python 时,直接调用 fork() 而不是通过 os.fork()(并返回或调用 Python)可能会导致 Python 内部锁之一的死锁由分叉后失效的线程持有。 PyOS_AfterFork_Child() 尝试重置必要的锁,但并不总是能够。

所有其他线程都消失的事实也意味着必须正确清除 CPython 的运行时状态,os.fork() 就是这样做的。 这意味着最终确定属于当前解释器的所有其他 PyThreadState 对象和所有其他 PyInterpreterState 对象。 由于这一点以及 “主”解释器 的特殊性,fork() 应该只在该解释器的“主”线程中调用,CPython 全局运行时最初是在该线程中初始化的。 唯一的例外是如果 exec() 将在之后立即调用。


高级 API

这些是编写 C 扩展代码或嵌入 Python 解释器时最常用的类型和函数:

type PyInterpreterState

该数据结构表示多个协作线程共享的状态。 属于同一个解释器的线程共享它们的模块管理和一些其他内部项目。 此结构中没有公共成员。

属于不同解释器的线程最初不共享任何东西,除了可用内存、打开的文件描述符等进程状态之外。 全局解释器锁也由所有线程共享,无论它们属于哪个解释器。

type PyThreadState
该数据结构表示单个线程的状态。 唯一的公共数据成员是 interp (PyInterpreterState*),它指向这个线程的解释器状态。
void PyEval_InitThreads()

不推荐使用的功能,它什么也不做。

在 Python 3.6 及更早版本中,如果 GIL 不存在,此函数会创建它。

3.9 版本变化: 该函数现在什么都不做。

3.7 版本变更: 该函数现在由 Py_Initialize() 调用,因此您不必再自己调用它了。

3.2 版本更改: 该函数不能在 Py_Initialize() 之前调用了。

int PyEval_ThreadsInitialized()

如果 PyEval_InitThreads() 已被调用,则返回一个非零值。 可以在不持有 GIL 的情况下调用此函数,因此可用于避免在运行单线程时调用锁定 API。

3.7 版更改: GIL 现在由 Py_Initialize() 初始化。

PyThreadState *PyEval_SaveThread()
释放全局解释器锁(如果已创建)并将线程状态重置为 NULL,返回之前的线程状态(不是 NULL)。 如果已创建锁,则当前线程必须已获取它。
void PyEval_RestoreThread(PyThreadState *tstate)

获取全局解释器锁(如果已经创建),设置线程状态为tstate,不能为NULL。 如果已创建锁,则当前线程一定没有获取它,否则会发生死锁。

笔记

在运行时结束时从线程调用此函数将终止该线程,即使该线程不是由 Python 创建的。 您可以在调用此函数之前使用 _Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否正在完成,以避免意外终止。

PyThreadState *PyThreadState_Get()
返回当前线程状态。 必须持有全局解释器锁。 当前线程状态为 NULL 时,会发出致命错误(因此调用者无需检查 NULL)。
PyThreadState *PyThreadState_Swap(PyThreadState *tstate)
将当前线程状态与参数 tstate 给出的线程状态交换,可能是 NULL。 全局解释器锁必须被持有并且不会被释放。

以下函数使用线程本地存储,并且与子解释器不兼容:

PyGILState_STATE PyGILState_Ensure()

确保当前线程准备好调用 Python C API,而不管 Python 或全局解释器锁的当前状态如何。 只要每个调用都与对 PyGILState_Release() 的调用相匹配,就可以根据需要多次调用它。 一般来说,其他线程相关的 API 可以在 PyGILState_Ensure()PyGILState_Release() 调用之间使用,只要线程状态恢复到 Release() 之前的先前状态即可。 例如,正常使用 Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS 宏是可以接受的。

返回值是调用 PyGILState_Ensure() 时线程状态的不透明“句柄”,并且必须传递给 PyGILState_Release() 以确保 Python 处于相同状态。 即使允许递归调用,这些句柄 也不能 共享 - 对 PyGILState_Ensure() 的每个唯一调用都必须保存对 PyGILState_Release() 的调用的句柄。

当函数返回时,当前线程将持有 GIL 并能够调用任意 Python 代码。 失败是致命的错误。

笔记

在运行时结束时从线程调用此函数将终止该线程,即使该线程不是由 Python 创建的。 您可以在调用此函数之前使用 _Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否正在完成,以避免意外终止。

void PyGILState_Release(PyGILState_STATE)

释放之前获得的任何资源。 在此调用之后,Python 的状态将与相应的 PyGILState_Ensure() 调用之前的状态相同(但通常调用者不知道此状态,因此使用 GILState API)。

PyGILState_Ensure() 的每次调用都必须与在同一线程上对 PyGILState_Release() 的调用相匹配。

PyThreadState *PyGILState_GetThisThreadState()
获取此线程的当前线程状态。 如果当前线程上没有使用 GILState API,则可能返回 NULL。 请注意,主线程始终具有这样的线程状态,即使没有在主线程上进行自动线程状态调用。 这主要是一个辅助/诊断功能。
int PyGILState_Check()

如果当前线程持有 GIL,则返回 1,否则返回 0。 可以随时从任何线程调用此函数。 只有当它初始化了 Python 线程状态并且当前持有 GIL 时,它才会返回 1。 这主要是一个辅助/诊断功能。 例如在回调上下文或内存分配函数中,当知道 GIL 被锁定可以允许调用者执行敏感操作或以其他方式表现不同时,它可能很有用。

3.4 版中的新功能。

以下宏通常不带尾随分号使用; 在 Python 源代码分发中查找示例用法。

Py_BEGIN_ALLOW_THREADS
该宏扩展为 { PyThreadState *_save; _save = PyEval_SaveThread();。 请注意,它包含一个左括号; 它必须与以下 Py_END_ALLOW_THREADS 宏匹配。 有关此宏的进一步讨论,请参见上文。
Py_END_ALLOW_THREADS
该宏扩展为 PyEval_RestoreThread(_save); }。 请注意,它包含一个右括号; 它必须与较早的 Py_BEGIN_ALLOW_THREADS 宏匹配。 有关此宏的进一步讨论,请参见上文。
Py_BLOCK_THREADS
这个宏扩展为 PyEval_RestoreThread(_save);:它相当于没有右大括号的 Py_END_ALLOW_THREADS
Py_UNBLOCK_THREADS
这个宏扩展为 _save = PyEval_SaveThread();:它相当于 Py_BEGIN_ALLOW_THREADS 没有左大括号和变量声明。


低级 API

以下所有函数都必须在 Py_Initialize() 之后调用。

3.7 版更改:Py_Initialize() 现在初始化 GIL


PyInterpreterState *PyInterpreterState_New()
创建一个新的解释器状态对象。 不需要持有全局解释器锁,但如果需要序列化对此函数的调用,则可以持有。
void PyInterpreterState_Clear(PyInterpreterState *interp)
重置解释器状态对象中的所有信息。 必须持有全局解释器锁。
void PyInterpreterState_Delete(PyInterpreterState *interp)
销毁解释器状态对象。 不需要持有全局解释器锁。 解释器状态必须在先前调用 PyInterpreterState_Clear() 时重置。
PyThreadState *PyThreadState_New(PyInterpreterState *interp)
创建一个属于给定解释器对象的新线程状态对象。 不需要持有全局解释器锁,但如果需要序列化对此函数的调用,则可以持有。
void PyThreadState_Clear(PyThreadState *tstate)

重置线程状态对象中的所有信息。 必须持有全局解释器锁。

3.9 版更改: 该函数现在调用 PyThreadState.on_delete 回调。 以前,这发生在 PyThreadState_Delete() 中。

void PyThreadState_Delete(PyThreadState *tstate)
销毁线程状态对象。 不需要持有全局解释器锁。 线程状态必须在先前调用 PyThreadState_Clear() 时重置。
void PyThreadState_DeleteCurrent(void)
销毁当前线程状态并释放全局解释器锁。 与 PyThreadState_Delete() 一样,不需要持有全局解释器锁。 线程状态必须在先前调用 PyThreadState_Clear() 时重置。
PyFrameObject *PyThreadState_GetFrame(PyThreadState *tstate)

获取Python线程状态tstate的当前帧。

返回一个强引用。 如果当前没有帧正在执行,则返回 NULL

另见 PyEval_GetFrame()

tstate 不能是 NULL

3.9 版中的新功能。

uint64_t PyThreadState_GetID(PyThreadState *tstate)

获取Python线程状态tstate的唯一线程状态标识符。

tstate 不能是 NULL

3.9 版中的新功能。

PyInterpreterState *PyThreadState_GetInterpreter(PyThreadState *tstate)

获取Python线程状态tstate的解释器。

tstate 不能是 NULL

3.9 版中的新功能。

PyInterpreterState *PyInterpreterState_Get(void)

获取当前的解释器。

如果没有当前的 Python 线程状态或没有当前的解释器,则发出致命错误。 它不能返回 NULL。

调用者必须持有 GIL。

3.9 版中的新功能。

int64_t PyInterpreterState_GetID(PyInterpreterState *interp)

返回解释器的唯一 ID。 如果这样做有任何错误,则返回 -1 并设置错误。

调用者必须持有 GIL。

3.7 版中的新功能。

PyObject *PyInterpreterState_GetDict(PyInterpreterState *interp)

返回一个字典,其中可以存储特定于解释器的数据。 如果此函数返回 NULL,则没有引发异常,调用者应假设没有特定于解释器的 dict 可用。

这不是 PyModule_GetState() 的替代品,扩展应该使用它来存储特定于解释器的状态信息。

3.8 版中的新功能。

typedef PyObject *(*_PyFrameEvalFunction)(PyThreadState *tstate, PyFrameObject *frame, int throwflag)

帧评估函数的类型。

throwflag 参数由生成器的 throw() 方法使用:如果非零,则处理当前异常。

3.9 版更改: 该函数现在采用 tstate 参数。

_PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp)

获取帧评估函数。

请参阅 PEP 523“向 CPython 添加框架评估 API”。

3.9 版中的新功能。

void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame)

设置帧评估函数。

请参阅 PEP 523“向 CPython 添加框架评估 API”。

3.9 版中的新功能。

PyObject *PyThreadState_GetDict()
返回一个字典,扩展可以在其中存储线程特定的状态信息。 每个扩展都应该使用一个唯一的键来存储字典中的状态。 当没有可用的当前线程状态时调用此函数是可以的。 如果此函数返回 NULL,则未引发异常,调用者应假定当前没有可用的线程状态。
int PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)

在线程中异步引发异常。 id 参数是目标线程的线程 id; exc 是要引发的异常对象。 此函数不会窃取对 exc 的任何引用。 为了防止天真的误用,您必须编写自己的 C 扩展来调用它。 必须在持有 GIL 的情况下调用。 返回修改的线程状态数; 这通常为 1,但如果未找到线程 ID,则该值为 0。 如果 excNULL,则清除线程的未决异常(如果有)。 这不会引发任何例外。

3.7 版本更改: id 参数的类型从 long 更改为 unsigned long

void PyEval_AcquireThread(PyThreadState *tstate)

获取全局解释器锁,设置当前线程状态为tstate,不能为NULL。 锁一定是更早创建的。 如果这个线程已经拥有锁,就会发生死锁。

笔记

在运行时结束时从线程调用此函数将终止该线程,即使该线程不是由 Python 创建的。 您可以在调用此函数之前使用 _Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否正在完成,以避免意外终止。

3.8 版本变更: 更新为与 PyEval_RestoreThread()Py_END_ALLOW_THREADS() 和 PyGILState_Ensure() 一致,并终止 X1当前线程,如果在解释器完成时调用。

PyEval_RestoreThread() 是一个始终可用的高级函数(即使线程尚未初始化)。

void PyEval_ReleaseThread(PyThreadState *tstate)

将当前线程状态重置为 NULL 并释放全局解释器锁。 锁必须早先创建并且必须由当前线程持有。 tstate 参数不能是 NULL,仅用于检查它是否代表当前线程状态——如果不是,则报告一个致命错误。

PyEval_SaveThread() 是一个始终可用的高级函数(即使线程尚未初始化)。

void PyEval_AcquireLock()

获取全局解释器锁。 锁一定是更早创建的。 如果这个线程已经拥有锁,就会发生死锁。

自 3.2 版起已弃用: 此函数不更新当前线程状态。 请改用 PyEval_RestoreThread()PyEval_AcquireThread()

笔记

在运行时结束时从线程调用此函数将终止该线程,即使该线程不是由 Python 创建的。 您可以在调用此函数之前使用 _Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否正在完成,以避免意外终止。

3.8 版本变更: 更新为与 PyEval_RestoreThread()Py_END_ALLOW_THREADS() 和 PyGILState_Ensure() 一致,并终止 X1当前线程,如果在解释器完成时调用。

void PyEval_ReleaseLock()

释放全局解释器锁。 锁一定是更早创建的。

自 3.2 版起已弃用: 此函数不更新当前线程状态。 请改用 PyEval_SaveThread()PyEval_ReleaseThread()


副译员支持

虽然在大多数用途中,您只会嵌入一个 Python 解释器,但在某些情况下,您需要在同一进程中甚至在同一线程中创建多个独立的解释器。 副口译员允许您这样做。

“主”解释器是运行时初始化时创建的第一个解释器。 它通常是进程中唯一的 Python 解释器。 与子解释器不同,主解释器具有独特的进程全局职责,如信号处理。 它还负责运行时初始化期间的执行,并且通常是运行时结束期间的活动解释器。 PyInterpreterState_Main() 函数返回一个指向其状态的指针。

您可以使用 PyThreadState_Swap() 函数在子解释器之间切换。 您可以使用以下函数创建和销毁它们:

PyThreadState *Py_NewInterpreter()

创建一个新的子解释器。 这是一个(几乎)完全独立的 Python 代码执行环境。 特别是,新的解释器具有所有导入模块的独立版本,包括基本模块 builtins__main__sys。 加载模块表(sys.modules)和模块搜索路径(sys.path)也是分开的。 新环境没有 sys.argv 变量。 它具有新的标准 I/O 流文件对象 sys.stdinsys.stdoutsys.stderr(但是这些是指相同的底层文件描述符)。

返回值指向在新子解释器中创建的第一个线程状态。 该线程状态是在当前线程状态下进行的。 请注意,没有创建实际线程; 请参阅下面对线程状态的讨论。 如果创建新的解释器不成功,则返回 NULL; 没有设置异常,因为异常状态存储在当前线程状态中,并且可能没有当前线程状态。 (与所有其他 Python/C API 函数一样,全局解释器锁必须在调用此函数之前保持,并在返回时仍保持;但是,与大多数其他 Python/C API 函数不同的是,在入口。)

扩展模块在(子)解释器之间共享,如下所示:

  • 对于使用多阶段初始化的模块,例如 PyModule_FromDefAndSpec(),为每个解释器创建并初始化一个单独的模块对象。 这些模块对象之间仅共享 C 级静态和全局变量。

  • 对于使用单阶段初始化的模块,例如 PyModule_Create(),第一次导入特定扩展时,它被正常初始化,并且其模块字典的(浅)副本被松动。 当另一个(子)解释器导入相同的扩展时,会初始化一个新模块并填充此副本的内容; 扩展的 init 函数未被调用。 因此,模块字典中的对象最终会在(子)解释器之间共享,这可能会导致不需要的行为(请参阅下面的 错误和警告 )。

    请注意,这与通过调用 Py_FinalizeEx()Py_Initialize() 完全重新初始化解释器后导入扩展时发生的情况不同; 在这种情况下,扩展的 initmodule 函数 is 再次被调用。 与多阶段初始化一样,这意味着这些模块之间仅共享 C 级静态和全局变量。

void Py_EndInterpreter(PyThreadState *tstate)
销毁由给定线程状态表示的(子)解释器。 给定的线程状态必须是当前线程状态。 请参阅下面对线程状态的讨论。 当调用返回时,当前线程状态为NULL。 与此解释器关联的所有线程状态都将被销毁。 (全局解释器锁必须在调用这个函数之前被持有,并且在它返回时仍然被持有。) Py_FinalizeEx() 将销毁所有当时没有被明确销毁的子解释器。

错误和警告

因为子解释器(和主解释器)是同一个进程的一部分,它们之间的隔离并不完美——例如,使用像 os.close() 这样的低级文件操作,它们可以(意外或恶意)影响彼此的打开文件。 由于(子)解释器之间共享扩展的方式,某些扩展可能无法正常工作; 这在使用单阶段初始化或(静态)全局变量时尤其有可能。 可以将在一个子解释器中创建的对象插入到另一个(子)解释器的命名空间中; 如果可能,应该避免这种情况。

应特别注意避免在子解释器之间共享用户定义的函数、方法、实例或类,因为由这些对象执行的导入操作可能会影响错误的(子)解释器加载模块的字典。 避免共享可访问上述对象的对象同样重要。

另请注意,将此功能与 PyGILState_* API 相结合是很微妙的,因为这些 API 假设 Python 线程状态和操作系统级线程之间存在双射,而子解释器的存在打破了这一假设。 强烈建议您不要在一对匹配的 PyGILState_Ensure()PyGILState_Release() 调用之间切换子解释器。 此外,使用这些 API 以允许从非 Python 创建的线程调用 Python 代码的扩展(例如 ctypes)在使用子解释器时可能会被破坏。


异步通知

提供了一种机制来向主解释器线程发出异步通知。 这些通知采用函数指针和空指针参数的形式。

int Py_AddPendingCall(int (*func)(void*), void *arg)

调度从主解释器线程调用的函数。 成功时,返回 0 并且 func 排队等待在主线程中调用。 失败时,返回 -1 而不设置任何异常。

当成功排队时,func 将被 最终 从主解释器线程调用,参数为 arg。 它将相对于正常运行的 Python 代码异步调用,但满足以下两个条件:

func 必须在成功时返回 0,或者在失败时返回 -1 并设置异常。 func不会被中断递归执行另一个异步通知,但如果全局解释器锁被释放,仍然可以被中断切换线程。

这个函数不需要当前线程状态来运行,也不需要全局解释器锁。

要在子解释器中调用此函数,调用者必须持有 GIL。 否则,函数 func 可能会被安排从错误的解释器中调用。

警告

这是一个低级函数,只对非常特殊的情况有用。 不能保证 func 会尽快被调用。 如果主线程正忙于执行系统调用,则在系统调用返回之前不会调用 func。 这个函数一般而不是适合从任意C线程调用Python代码。 相反,使用 PyGILState API

在 3.9 版更改: 如果在子解释器中调用此函数,则函数 func 现在计划从子解释器调用,而不是从主解释器调用。 每个子解释器现在都有自己的预定调用列表。

3.1 版中的新功能。


分析和跟踪

Python 解释器为附加分析和执行跟踪工具提供了一些低级支持。 这些用于分析、调试和覆盖率分析工具。

此 C 接口允许分析或跟踪代码避免通过 Python 级可调用对象进行调用的开销,而是进行直接 C 函数调用。 设施的基本属性没有改变; 该接口允许为每个线程安装跟踪函数,并且报告给跟踪函数的基本事件与之前版本中报告给 Python 级跟踪函数的基本事件相同。

typedef int (*Py_tracefunc)(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg)

使用 PyEval_SetProfile()PyEval_SetTrace() 注册的跟踪函数的类型。 第一个参数是传递给注册函数的对象objframe是事件所属的帧对象,what是其中一个常量[ X189X]、PyTrace_EXCEPTIONPyTrace_LINEPyTrace_RETURNPyTrace_C_CALLPyTrace_C_EXCEPTION、[X252X] X273X] 和 arg 取决于 what 的值:

what的值

arg 的含义

PyTrace_CALL

总是 Py_None

PyTrace_EXCEPTION

sys.exc_info() 返回的异常信息。

PyTrace_LINE

总是 Py_None

PyTrace_RETURN

返回给调用者的值,如果由异常引起,则返回 NULL

PyTrace_C_CALL

被调用的函数对象。

PyTrace_C_EXCEPTION

被调用的函数对象。

PyTrace_C_RETURN

被调用的函数对象。

PyTrace_OPCODE

总是 Py_None

int PyTrace_CALL
Py_tracefunc 函数的 what 参数值,当报告对函数或方法的新调用,或生成器的新条目时。 请注意,不会报告为生成器函数创建迭代器,因为没有控制转移到相应帧中的 Python 字节码。
int PyTrace_EXCEPTION
引发异常时 Py_tracefunc 函数的 what 参数的值。 在处理任何字节码之后,在正在执行的帧内设置异常后,将使用 what 的此值调用回调函数。 这样做的效果是,当异常传播导致 Python 堆栈展开时,回调会在异常传播时返回到每一帧时调用。 只有跟踪函数接收这些事件; 分析器不需要它们。
int PyTrace_LINE
当报告行号事件时,作为 what 参数传递给 Py_tracefunc 函数(但不是分析函数)的值。 可以通过在该帧上将 f_trace_lines 设置为 0 来禁用该帧。
int PyTrace_RETURN
Py_tracefunc 函数的 what 参数值,当调用即将返回时。
int PyTrace_C_CALL
当 C 函数即将被调用时, Py_tracefunc 函数的 what 参数的值。
int PyTrace_C_EXCEPTION
当 C 函数引发异常时, Py_tracefunc 函数的 what 参数的值。
int PyTrace_C_RETURN
当 C 函数返回时, Py_tracefunc 函数的 what 参数的值。
int PyTrace_OPCODE
即将执行新操作码时, Py_tracefunc 函数(但不是分析函数)的 what 参数的值。 默认情况下不发出此事件:必须通过在框架上将 f_trace_opcodes 设置为 1 来明确请求。
void PyEval_SetProfile(Py_tracefunc func, PyObject *obj)

将探查器功能设置为 funcobj 参数作为第一个参数传递给函数,可以是任何 Python 对象,或 NULL。 如果 profile 函数需要维护状态,则为每个线程使用不同的 obj 值提供了一个方便且线程安全的地方来存储它。 除了 PyTrace_LINEPyTrace_OPCODEPyTrace_EXCEPTION 之外,所有被监控的事件都会调用 profile 函数。

调用者必须持有 GIL

void PyEval_SetTrace(Py_tracefunc func, PyObject *obj)

将跟踪功能设置为func。 这类似于 PyEval_SetProfile(),除了跟踪函数确实接收行号事件和每个操作码事件,但不接收与被调用的 C 函数对象相关的任何事件。 任何使用 PyEval_SetTrace() 注册的跟踪函数都不会接收 PyTrace_C_CALLPyTrace_C_EXCEPTIONPyTrace_C_RETURN 作为 what 参数的值.

调用者必须持有 GIL


高级调试器支持

这些函数仅供高级调试工具使用。

PyInterpreterState *PyInterpreterState_Head()
返回位于所有此类对象列表开头的解释器状态对象。
PyInterpreterState *PyInterpreterState_Main()
返回主解释器状态对象。
PyInterpreterState *PyInterpreterState_Next(PyInterpreterState *interp)
从所有此类对象的列表中返回 interp 之后的下一个解释器状态对象。
PyThreadState *PyInterpreterState_ThreadHead(PyInterpreterState *interp)
返回指向与解释器 interp 关联的线程列表中第一个 PyThreadState 对象的指针。
PyThreadState *PyThreadState_Next(PyThreadState *tstate)
从属于同一 PyInterpreterState 对象的所有此类对象的列表中返回 tstate 之后的下一个线程状态对象。


线程本地存储支持

Python 解释器提供对线程本地存储 (TLS) 的低级支持,它封装了底层原生 TLS 实现以支持 Python 级线程本地存储 API (threading.local)。 CPython C 级 API 类似于 pthreads 和 Windows 提供的 API:使用线程键和函数来关联每个线程的 void* 值。

GIL在调用这些函数时不需要而不是; 他们提供自己的锁。

请注意,Python.h 不包括 TLS API 的声明,您需要包括 pythread.h 才能使用线程本地存储。

笔记

这些 API 函数都没有代表 void* 值处理内存管理。 您需要自己分配和释放它们。 如果 void* 值恰好是 PyObject*,这些函数也不会对它们进行引用计数操作。


线程特定存储 (TSS) API

引入 TSS API 是为了取代 CPython 解释器中现有 TLS API 的使用。 此 API 使用新类型 Py_tss_t 而不是 int 来表示线程键。

3.7 版中的新功能。


也可以看看

“CPython 中用于线程本地存储的新 C-API”(PEP 539


type Py_tss_t

该数据结构表示线程键的状态,其定义可能取决于底层 TLS 实现,并且它有一个内部字段表示键的初始化状态。 此结构中没有公共成员。

Py_LIMITED_API 未定义时,允许通过 Py_tss_NEEDS_INIT 静态分配此类型。

Py_tss_NEEDS_INIT
此宏扩展为 Py_tss_t 变量的初始值设定项。 请注意,此宏不会用 Py_LIMITED_API 定义。

动态分配

Py_tss_t 的动态分配,在使用 Py_LIMITED_API 构建的扩展模块中是必需的,由于它的实现在构建时不透明,因此无法进行静态分配。

Py_tss_t *PyThread_tss_alloc()
在动态分配失败的情况下,返回与使用 Py_tss_NEEDS_INITNULL 初始化的值相同状态的值。
void PyThread_tss_free(Py_tss_t *key)

释放由 PyThread_tss_alloc() 分配的给定 key,在第一次调用 PyThread_tss_delete() 以确保任何关联的线程局部变量都已取消分配之后。 如果 key 参数是 NULL,这是一个空操作。

笔记

一个被释放的键变成了一个悬空指针,你应该将键重置为 NULL。


方法

这些功能的参数key不能是NULL。 此外,PyThread_tss_set()PyThread_tss_get() 的行为是未定义的,如果给定的 Py_tss_t 没有被 Py_create5(X160X]X18ThreadX) .

int PyThread_tss_is_created(Py_tss_t *key)
如果给定的 Py_tss_t 已被 PyThread_tss_create() 初始化,则返回一个非零值。
int PyThread_tss_create(Py_tss_t *key)
成功初始化 TSS 密钥时返回零值。 如果 key 参数指向的值不是由 Py_tss_NEEDS_INIT 初始化,则行为未定义。 这个函数可以在同一个键上重复调用——在一个已经初始化的键上调用它是一个空操作,并立即返回成功。
void PyThread_tss_delete(Py_tss_t *key)
销毁 TSS 密钥以忘记与所有线程中的密钥关联的值,并将密钥的初始化状态更改为未初始化。 销毁的密钥可以通过 PyThread_tss_create() 再次初始化。 这个函数可以在同一个键上重复调用——在一个已经销毁的键上调用它是一个空操作。
int PyThread_tss_set(Py_tss_t *key, void *value)
返回零值以指示成功将 void* 值与当前线程中的 TSS 键关联。 每个线程都有一个不同的键到 void* 值的映射。
void *PyThread_tss_get(Py_tss_t *key)
返回与当前线程中的 TSS 密钥关联的 void* 值。 如果当前线程中没有与键关联的值,则返回 NULL


线程本地存储 (TLS) API

自 3.7 版起已弃用:此 API 已被 线程特定存储 (TSS) API 取代。


笔记

此版本的 API 不支持以无法安全地转换为 int 的方式定义本机 TLS 密钥的平台。 在此类平台上,PyThread_create_key() 将立即返回失败状态,并且其他 TLS 函数在此类平台上都将无操作。


由于上述兼容性问题,此版本的 API 不应在新代码中使用。

int PyThread_create_key()
void PyThread_delete_key(int key)
int PyThread_set_key_value(int key, void *value)
void *PyThread_get_key_value(int key)
void PyThread_delete_key_value(int key)
void PyThread_ReInitTLS()