初始化、终结和线程 — Python 文档
初始化、结束和线程
Python初始化之前
在嵌入 Python 的应用程序中,必须在使用任何其他 Python/C API 函数之前调用 Py_Initialize() 函数; 除了一些函数和全局配置变量。
在 Python 初始化之前可以安全地调用以下函数:
- 配置功能:
PyImport_AppendInittab()
PyImport_ExtendInittab()
PyInitFrozenExtensions()
PyMem_SetAllocator()
PyMem_SetupDebugHooks()
PyObject_SetArenaAllocator()
Py_SetPath()
Py_SetProgramName()
Py_SetPythonHome()
Py_SetStandardStreamEncoding()
PySys_AddWarnOption()
PySys_AddXOption()
PySys_ResetWarnOptions()
- 信息功能:
Py_IsInitialized()
PyMem_GetAllocator()
PyObject_GetArenaAllocator()
Py_GetBuildInfo()
Py_GetCompiler()
Py_GetCopyright()
Py_GetPlatform()
Py_GetVersion()
- 实用程序:
- 内存分配器:
全局配置变量
Python 具有用于全局配置的变量来控制不同的功能和选项。 默认情况下,这些标志由 命令行选项 控制。
通过选项设置标志时,标志的值是选项所设置的次数。 例如,-b
将 Py_BytesWarningFlag
设置为 1,而 -bb
将 Py_BytesWarningFlag
设置为 2。
- Py_DebugFlag
打开解析器调试输出(仅限专家,取决于编译选项)。
由 -d 选项和 PYTHONDEBUG 环境变量设置。
- Py_DontWriteBytecodeFlag
如果设置为非零,Python 将不会尝试在导入源模块时写入
.pyc
文件。由 -B 选项和 PYTHONDONTWRITEBYTECODE 环境变量设置。
- Py_FrozenFlag
在 Py_GetPath() 中计算模块搜索路径时抑制错误消息。
_freeze_importlib
和frozenmain
程序使用的私有标志。
- Py_HashRandomizationFlag
如果 PYTHONHASHSEED 环境变量设置为非空字符串,则设置为
1
。如果标志不为零,则读取 PYTHONHASHSEED 环境变量以初始化秘密哈希种子。
- Py_IgnoreEnvironmentFlag
忽略所有
PYTHON*
环境变量,例如 PYTHONPATH 和 PYTHONHOME,可以设置。
- Py_InspectFlag
当脚本作为第一个参数传递或使用 -c 选项时,在执行脚本或命令后进入交互模式,即使 sys.stdin 似乎不是一个终端。
由 -i 选项和 PYTHONINSPECT 环境变量设置。
- Py_InteractiveFlag
- 由 -i 选项设置。
- Py_LegacyWindowsFSEncodingFlag
如果标志非零,则使用
mbcs
编码而不是文件系统编码的 UTF-8 编码。如果 PYTHONLEGACYWINDOWSFSENCODING 环境变量设置为非空字符串,则设置为
1
。有关更多详细信息,请参阅 PEP 529。
- Py_LegacyWindowsStdioFlag
如果标志非零,则使用 io.FileIO 而不是
WindowsConsoleIO
用于 sys 标准流。如果 PYTHONLEGACYWINDOWSSTDIO 环境变量设置为非空字符串,则设置为
1
。有关更多详细信息,请参阅 PEP 528。
- Py_NoSiteFlag
禁用模块 site 的导入和它需要的 sys.path 的站点相关操作。 如果稍后显式导入 site,也请禁用这些操作(如果您希望它们被触发,请调用 site.main())。
由 -S 选项设置。
- Py_NoUserSiteDirectory
-
由 -s 和 -I 选项以及 PYTHONNOUSERSITE 环境变量设置。
- Py_OptimizeFlag
- 由 -O 选项和 PYTHONOPTIMIZE 环境变量设置。
- Py_QuietFlag
即使在交互模式下也不显示版权和版本消息。
由 -q 选项设置。
3.2 版中的新功能。
- Py_UnbufferedStdioFlag
强制 stdout 和 stderr 流无缓冲。
由 -u 选项和 PYTHONUNBUFFERED 环境变量设置。
- 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)
- 如果 initsigs 是
1
,则此函数的工作方式类似于 Py_Initialize()。 如果 initsigs 是0
,它会跳过信号处理程序的初始化注册,这在嵌入 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 版本!)。
系统管理员将知道如何配置 mount 或 automount 程序以在平台之间共享
/usr/local
,同时让/usr/local/plat
成为每个平台的不同文件系统。
- wchar_t *Py_GetProgramFullPath()
- 返回 Python 可执行文件的完整程序名称; 这被计算为从程序名称(由上面的 Py_SetProgramName() 设置)导出默认模块搜索路径的副作用。 返回的字符串指向静态存储; 调用者不应修改其值。 该值可用于 Python 代码作为
sys.executable
。
- wchar_t *Py_GetPath()
- 返回默认模块搜索路径; 这是根据程序名称(由上面的 Py_SetProgramName() 设置)和一些环境变量计算得出的。 返回的字符串由一系列由平台相关的定界符分隔的目录名称组成。 分隔符在 Unix 和 Mac OS X 上是
':'
,在 Windows 上是';'
。 返回的字符串指向静态存储; 调用者不应修改其值。 列表 sys.path 在解释器启动时用这个值初始化; 它可以(通常是)稍后修改以更改加载模块的搜索路径。
- void Py_SetPath(const wchar_t*)
设置默认模块搜索路径。 如果在 Py_Initialize() 之前调用此函数,则 Py_GetPath() 将不会尝试计算默认搜索路径,而是使用提供的搜索路径。 如果 Python 是由完全了解所有模块位置的应用程序嵌入的,这将非常有用。 路径组件应由平台相关的定界符分隔,在 Unix 和 Mac OS X 上为
':'
,在 Windows 上为';'
。这也会导致 sys.executable 仅设置为原始程序名称(参见 Py_SetProgramName())以及 sys.prefix 和 sys。 exec_prefix 为空。 如果需要,调用者可以在调用 Py_Initialize() 后修改这些。
使用 Py_DecodeLocale() 解码字节字符串以获得
wchar_*
字符串。路径参数在内部复制,因此调用者可以在调用完成后释放它。
- 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'
。 在 Mac OS X 上,它是'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)
根据 argc 和 argv 设置 sys.argv。 这些参数类似于传递给程序的
main()
函数的参数,不同之处在于第一个条目应该引用要执行的脚本文件,而不是托管 Python 解释器的可执行文件。 如果没有要运行的脚本,argv 中的第一个条目可以是空字符串。 如果此函数无法初始化 sys.argv,则会使用 Py_FatalError() 发出致命条件信号。如果 updatepath 为零,这就是函数所做的全部。 如果 updatepath 非零,该函数还会根据以下算法修改 sys.path:
如果在
argv[0]
中传递现有脚本的名称,则脚本所在目录的绝对路径将被添加到 sys.path。否则(即,如果 argc 是
0
或argv[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);
以下是这些函数的工作原理:全局解释器锁用于保护指向当前线程状态的指针。 在释放锁和保存线程状态时,必须在释放锁之前检索当前线程状态指针(因为另一个线程可以立即获取锁并将自己的线程状态存储在全局变量中)。 反之,在获取锁和恢复线程状态时,必须在存储线程状态指针之前获取锁。
非 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。
关于线程的另一个需要注意的重要事项是它们在面对 C fork()
调用时的行为。 在大多数具有 fork()
的系统上,在进程 fork 之后,只有发出 fork 的线程才会存在。 这也意味着其他线程持有的任何锁永远不会被释放。 Python 为 os.fork() 解决了这个问题,方法是在 fork 之前获取它内部使用的锁,然后释放它们。 此外,它会重置子进程中的任何 锁定对象 。 在扩展或嵌入 Python 时,无法通知 Python 需要在 fork 之前获取或在 fork 之后重置的其他(非 Python)锁。 需要使用诸如 pthread_atfork()
之类的操作系统工具来完成相同的事情。 此外,在扩展或嵌入 Python 时,直接调用 fork()
而不是通过 os.fork()(并返回或调用 Python)可能会导致 Python 内部锁之一的死锁由一个在分叉后失效的线程持有。 PyOS_AfterFork_Child() 尝试重置必要的锁,但并不总是能够。
高级 API
这些是编写 C 扩展代码或嵌入 Python 解释器时最常用的类型和函数:
- type PyInterpreterState
该数据结构表示多个协作线程共享的状态。 属于同一个解释器的线程共享它们的模块管理和一些其他内部项目。 此结构中没有公共成员。
属于不同解释器的线程最初不共享任何东西,除了可用内存、打开文件描述符等进程状态。 全局解释器锁也由所有线程共享,无论它们属于哪个解释器。
- type PyThreadState
- 该数据结构表示单个线程的状态。 唯一的公共数据成员是
PyInterpreterState *
interp
,它指向这个线程的解释器状态。
- void PyEval_InitThreads()
初始化并获取全局解释器锁。 在创建第二个线程或进行任何其他线程操作(例如
PyEval_ReleaseThread(tstate)
)之前,应在主线程中调用它。 在调用 PyEval_SaveThread() 或 PyEval_RestoreThread() 之前不需要它。这是第二次调用时的空操作。
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
。 全局解释器锁必须被持有并且不会被释放。
- void PyEval_ReInitThreads()
- 这个函数是从 PyOS_AfterFork_Child() 中调用的,以确保新创建的子进程不会持有引用未在子进程中运行的线程的锁。
以下函数使用线程本地存储,并且与子解释器不兼容:
- PyGILState_STATE PyGILState_Ensure()
确保当前线程准备好调用 Python C API,而不管 Python 或全局解释器锁的当前状态如何。 只要每个调用都与对 PyGILState_Release() 的调用相匹配,就可以根据需要多次调用它。 一般而言,只要线程状态恢复到 Release() 之前的先前状态,就可以在 PyGILState_Ensure() 和 PyGILState_Release() 调用之间使用其他线程相关的 API。 例如,Py_BEGIN_ALLOW_THREADS 和 Py_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)
- 重置线程状态对象中的所有信息。 必须持有全局解释器锁。
- void PyThreadState_Delete(PyThreadState *tstate)
- 销毁线程状态对象。 不需要持有全局解释器锁。 线程状态必须在先前调用 PyThreadState_Clear() 时被重置。
- PY_INT64_T PyInterpreterState_GetID(PyInterpreterState *interp)
返回解释器的唯一 ID。 如果这样做有任何错误,则返回
-1
并设置错误。3.7 版中的新功能。
- PyObject *PyThreadState_GetDict()
- 返回一个字典,扩展可以在其中存储线程特定的状态信息。 每个扩展都应该使用一个唯一的键来存储字典中的状态。 当没有可用的当前线程状态时调用此函数是可以的。 如果此函数返回
NULL
,则未引发异常,调用者应假定当前没有可用的线程状态。
- int PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
在线程中异步引发异常。 id 参数是目标线程的线程 id; exc 是要引发的异常对象。 此函数不会窃取对 exc 的任何引用。 为了防止天真的误用,您必须编写自己的 C 扩展来调用它。 必须在持有 GIL 的情况下调用。 返回修改的线程状态数; 这通常为 1,但如果未找到线程 ID,则该值为 0。 如果 exc 是
NULL
,则清除线程的未决异常(如果有)。 这不会引发任何例外。3.7 版本变更: id 参数的类型由
long
变更为unsigned long
。
- void PyEval_AcquireThread(PyThreadState *tstate)
获取全局解释器锁,设置当前线程状态为tstate,不应为
NULL
。 锁一定是更早创建的。 如果这个线程已经拥有锁,就会发生死锁。PyEval_RestoreThread() 是一个始终可用的高级函数(即使线程尚未初始化)。
- void PyEval_ReleaseThread(PyThreadState *tstate)
将当前线程状态重置为
NULL
并释放全局解释器锁。 锁必须早先创建并且必须由当前线程持有。 tstate 参数不能是NULL
,仅用于检查它是否代表当前线程状态——如果不是,则报告一个致命错误。PyEval_SaveThread() 是一个始终可用的高级函数(即使线程尚未初始化)。
- void PyEval_AcquireLock()
获取全局解释器锁。 锁一定是更早创建的。 如果这个线程已经拥有锁,就会发生死锁。
自 3.2 版起已弃用: 此函数不更新当前线程状态。 请改用 PyEval_RestoreThread() 或 PyEval_AcquireThread()。
- void PyEval_ReleaseLock()
释放全局解释器锁。 锁一定是更早创建的。
自 3.2 版起已弃用: 此函数不更新当前线程状态。 请改用 PyEval_SaveThread() 或 PyEval_ReleaseThread()。
副译员支持
虽然在大多数用途中,您只会嵌入一个 Python 解释器,但在某些情况下,您需要在同一进程中甚至在同一线程中创建多个独立的解释器。 副口译员允许您这样做。 您可以使用 PyThreadState_Swap() 函数在子解释器之间切换。 您可以使用以下函数创建和销毁它们:
- PyThreadState *Py_NewInterpreter()
创建一个新的子解释器。 这是一个(几乎)完全独立的 Python 代码执行环境。 特别是,新解释器具有所有导入模块的单独、独立版本,包括基本模块 builtins、__main__ 和 sys。 加载模块表(
sys.modules
)和模块搜索路径(sys.path
)也是分开的。 新环境没有sys.argv
变量。 它具有新的标准 I/O 流文件对象sys.stdin
、sys.stdout
和sys.stderr
(但是这些指的是相同的底层文件描述符)。返回值指向在新子解释器中创建的第一个线程状态。 该线程状态是在当前线程状态下进行的。 请注意,没有创建实际线程; 请参阅下面对线程状态的讨论。 如果创建新的解释器不成功,则返回
NULL
; 没有设置异常,因为异常状态存储在当前线程状态中,并且可能没有当前线程状态。 (与所有其他 Python/C API 函数一样,全局解释器锁必须在调用此函数之前保持,并在返回时仍保持;但是,与大多数其他 Python/C API 函数不同的是,在入口。)扩展模块在(子)解释器之间共享如下:第一次导入特定扩展时,它被正常初始化,并且其模块字典的(浅)副本被松散开。 当另一个(子)解释器导入相同的扩展时,会初始化一个新模块并填充此副本的内容; 扩展的
init
函数未被调用。 请注意,这与通过调用 Py_FinalizeEx() 和 Py_Initialize() 完全重新初始化解释器后导入扩展时发生的情况不同; 在这种情况下,扩展的initmodule
函数 is 再次被调用。
- 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 不会被中断递归执行另一个异步通知,但如果全局解释器锁被释放,仍然可以被中断切换线程。这个函数不需要当前线程状态来运行,也不需要全局解释器锁。
警告
这是一个低级函数,只对非常特殊的情况有用。 不能保证 func 会尽快被调用。 如果主线程正忙于执行系统调用,则在系统调用返回之前不会调用 func。 这个函数通常 不 适合从任意 C 线程调用 Python 代码。 相反,使用 PyGILState API 。
3.1 版中的新功能。
分析和跟踪
Python 解释器为附加分析和执行跟踪工具提供了一些低级支持。 这些用于分析、调试和覆盖率分析工具。
此 C 接口允许分析或跟踪代码避免通过 Python 级可调用对象进行调用的开销,而是进行直接 C 函数调用。 设施的基本属性没有改变; 该接口允许为每个线程安装跟踪函数,并且报告给跟踪函数的基本事件与之前版本中报告给 Python 级跟踪函数的基本事件相同。
- typedef int (*Py_tracefunc)(PyObject *obj, PyFrameObject *frame, int what, PyObject *arg)
使用 PyEval_SetProfile() 和 PyEval_SetTrace() 注册的跟踪函数的类型。 第一个参数是传递给注册函数的对象obj,frame是事件所属的帧对象,what是其中一个常量[ X189X]、
PyTrace_EXCEPTION
、PyTrace_LINE
、PyTrace_RETURN
、PyTrace_C_CALL
、PyTrace_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
- 当报告对函数或方法的新调用或生成器的新条目时,what 函数的 Py_tracefunc 函数的值。 请注意,不会报告为生成器函数创建迭代器,因为没有控制转移到相应帧中的 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 函数即将被调用时,what 参数到 Py_tracefunc 函数的值。
- int PyTrace_C_EXCEPTION
- Py_tracefunc 的 what 参数的值在 C 函数引发异常时起作用。
- int PyTrace_C_RETURN
- 当 C 函数返回时, Py_tracefunc 函数的 what 参数的值。
- int PyTrace_OPCODE
- 即将执行新操作码时,what 参数到 Py_tracefunc 函数(但不是分析函数)的值。 默认情况下不发出此事件:必须通过在框架上将
f_trace_opcodes
设置为 1 来明确请求。
- void PyEval_SetProfile(Py_tracefunc func, PyObject *obj)
- 将探查器功能设置为 func。 obj 参数作为第一个参数传递给函数,可以是任何 Python 对象,或
NULL
。 如果 profile 函数需要维护状态,则为每个线程使用不同的 obj 值提供了一个方便且线程安全的地方来存储它。 除了PyTrace_LINE
、PyTrace_OPCODE
和PyTrace_EXCEPTION
之外,所有监控事件都会调用配置文件函数。
- void PyEval_SetTrace(Py_tracefunc func, PyObject *obj)
- 将跟踪功能设置为 func。 这类似于 PyEval_SetProfile(),除了跟踪函数确实接收行号事件和每个操作码事件,但不接收任何与被调用的 C 函数对象相关的事件。 任何使用 PyEval_SetTrace() 注册的跟踪函数都不会接收
PyTrace_C_CALL
、PyTrace_C_EXCEPTION
或PyTrace_C_RETURN
作为 what 参数的值.
高级调试器支持
这些函数仅供高级调试工具使用。
- 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 版中的新功能。
- 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_INIT 或
NULL
初始化的值相同状态的值。
- void PyThread_tss_free(Py_tss_t *key)
在第一次调用 PyThread_tss_delete() 以确保任何关联的线程局部变量未被分配后,释放由 PyThread_tss_alloc() 分配的给定 key。 如果 key 参数为 NULL,则这是一个空操作。
笔记
一个被释放的键变成了一个悬空指针,你应该将键重置为 NULL。
方法
这些功能的参数key不能是NULL
。 此外,如果给定的 Py_tss_t 没有被 PyThread_tss_create() 初始化,则 PyThread_tss_set() 和 PyThread_tss_get() 的行为是未定义的.
- 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()