初始化,最终化和线程

请参阅 Python 初始化配置 了解如何在初始化之前配置解释器的详情。

在Python初始化之前

在一个植入了 Python 的应用程序中,Py_Initialize() 函数必须在任何其他 Python/C API 函数之前被调用;例外的只有个别函数和 全局配置变量

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

备注

虽然它们看起来与上面列出的某些函数类似,但以下函数 不应 在解释器被初始化之前调用: Py_EncodeLocale(), Py_GetPath(), Py_GetPrefix(), Py_GetExecPrefix(), Py_GetProgramFullPath(), Py_GetPythonHome(), Py_GetProgramName(), PyEval_InitThreads()Py_RunMain()

全局配置变量

Python 有负责控制全局配置中不同特性和选项的变量。这些标志默认被 命令行选项

当一个选项设置一个旗标时,该旗标的值将是设置选项的次数。 例如,-b 会将 Py_BytesWarningFlag 设为 1 而 -bb 会将 Py_BytesWarningFlag 设为 2.

  • int Py_BytesWarningFlag

当将 bytesbytearraystr 比较或者将 bytesint 比较时发出警告。 如果大于等于 2 则报错。

-b 选项设置。

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_DebugFlag

开启解析器调试输出(限专家使用,依赖于编译选项)。

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

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_DontWriteBytecodeFlag

如果设置为非零, Python 不会在导入源代码时尝试写入 .pyc 文件

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

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_FrozenFlag

当在 Py_GetPath() 中计算模块搜索路径时屏蔽错误消息。

freezeimportlibfrozenmain 程序使用的私有旗标。

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_HashRandomizationFlag

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

如果该旗标为非零值,则读取 PYTHONHASHSEED 环境变量来初始化加密哈希种子。

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_IgnoreEnvironmentFlag

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

-E-I 选项设置。

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_InspectFlag

当将脚本作为第一个参数传入或是使用了 -c 选项时,则会在执行该脚本或命令后进入交互模式,即使在 sys.stdin 并非一个终端时也是如此。

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

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_InteractiveFlag

-i 选项设置。

自 3.12 版本弃用.

  • int Py_IsolatedFlag

以隔离模式运行 Python. 在隔离模式下 sys.path 将不包含脚本的目录或用户的 site-packages 目录。

-I 选项设置。

Added in version 3.4.

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_LegacyWindowsFSEncodingFlag

如果该旗标为非零值,则使用 mbcs 编码和replace 错误处理器,而不是 UTF-8 编码和 surrogatepass 错误处理器作用 filesystem encoding and error handler

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

更多详情请参阅 PEP 529 [https://peps.python.org/pep-0529/]。

Availability: Windows.

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_LegacyWindowsStdioFlag

如果该旗标为非零值,则会使用 io.FileIO 而不是 io._WindowsConsoleIO 作为 sys 标准流。

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

有关更多详细信息,请参阅 PEP 528 [https://peps.python.org/pep-0528/]。

Availability: Windows.

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_NoSiteFlag

禁用 site 的导入及其所附带的基于站点对 sys.path 的操作。 如果 site 会在稍后被显式地导入也会禁用这些操作 (如果你希望触发它们则应调用 site.main())。

-S 选项设置。

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_NoUserSiteDirectory

不要将 用户 site-packages 目录 添加到 sys.path

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

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_OptimizeFlag

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

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_QuietFlag

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

-q 选项设置。

Added in version 3.2.

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_UnbufferedStdioFlag

强制 stdout 和 stderr 流不带缓冲。

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

Deprecated since version 3.12, will be removed in version 3.14.

  • int Py_VerboseFlag

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

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

Deprecated since version 3.12, will be removed in version 3.14.

初始化和最终化解释器

  • void Py_Initialize()
  • 属于 稳定 ABI. 初始化 Python 解释器。 在嵌入 Python 的应用程序中,它应当在使用任何其他 Python/C API 函数之前被调用;请参阅 在 Python 初始化之前 了解少数的例外情况。

这将初始化已加载的模块表 (sys.modules),并创建基础模块 builtins, __main__sys。 它还会初始化模块搜索路径 (sys.path)。 它不会设置 sys.argv;对于此设置请使用 Python 初始化配置 API。 当第二次被调用时(在未先调用 Py_FinalizeEx() 的情况下)将不会执行任何操作。 它没有返回值;如果初始化失败则会发生致命错误。

使用 Py_InitializeFromConfig() 来自定义 Python 初始化配置

备注

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

  • void Py_InitializeEx(int initsigs)
  • 属于 稳定 ABI. 如果 initsigs 为 1 则此函数的工作方式与 Py_Initialize() 类似。 如果 initsigs 为 0,它将跳过信号处理器的初始化注册,这在将 CPython 作为更大应用程序的一部分嵌入时会很有用处。

使用 Py_InitializeFromConfig() 来自定义 Python 初始化配置

请参阅 Python初始化配置 一节了解有关预初始化解释器,填充运行时配置结构体,以及查询所返回的状态结构体的详情。

  • int Py_IsInitialized()
  • 属于 稳定 ABI. 如果 Python 解释器已初始化,则返回真值(非零);否则返回假值(零)。 在调用 Py_FinalizeEx() 之后,此函数将返回假值直到 Py_Initialize() 再次被调用。
  • int Py_IsFinalizing()
  • 属于 稳定 ABI 自 3.13 版起. 如果主 Python 解释器 正在关闭 则返回真(非零)值。 在其他情况下返回假(零)值。

Added in version 3.13.

  • int Py_FinalizeEx()
  • 属于 稳定 ABI 自 3.6 版起. 撤销 Py_Initialize() 所做的所有初始化和随后对 Python/C API 函数的使用,并销毁自上次调用 Py_Initialize() 以来创建但尚未销毁的所有子解释器 (见下文 Py_NewInterpreter())。 在理想情况下,这会释放 Python 解释器分配的所有内存。 当第二次调用时(在没有再次调用 Py_Initialize() 的情况下),该函数不执行任何操作。

由于这是 Py_Initialize() 的逆向操作,因而它应当在激活同一解释器的同一线程中被调用。 这意味着主线程和主解释器。 当 Py_RunMain() 仍然运行时则绝不应调用此函数。

通常返回值为 0。 如果在最终化(刷新缓冲的数据)期间发生错误,则返回 -1

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

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

引发一个不带参数的 审计事件 cpython._PySys_ClearAuditHooks

Added in version 3.6.

  • void Py_Finalize()
  • int Py_BytesMain(int argc, char **argv)
  • 属于 稳定 ABI 自 3.8 版起. 类似于 Py_Main() 但 argv 是一个字节串数组,允许调用方应用程序将文本编码步骤委托给 CPython 运行时。

Added in version 3.8.

  • int Py_Main(int argc, wchar_t **argv)
  • 属于 稳定 ABI. 标准解释器的主程序,封装了完整的初始化/最终化循环,以及一些附加行为以实现从环境和命令行读取配置设置,然后按照 命令行 的规则执行 __main__

这适用于希望支持完整 CPython 命令行界面的程序,而不仅是在更大应用程序中嵌入 Python 运行时。

argc 和 argv 形参与传给 C 程序的 main() 函数的形参类似,不同之处在于 argv 的条目会先使用 Py_DecodeLocale() 转换为 wchar_t。 还有一个重要的注意事项是参数列表条目可能会被修改为指向并非被传入的字符串(不过,参数列表所指向的字符串内容不会被修改)。

如果解释器正常退出(即未引发异常)则返回值将为 0,如果解释器因异常而退出则返回 1,或者如果参数列表不是合法的 Python 命令行则返回 2

请注意如果引发了未在其他地方被处理的 SystemExit,则此函数将不返回 1,但会退出进程,如果 Py_InspectFlag 未被设置的话。 如果设置了 Py_InspectFlag,执行将退至交互式 Python 提示符,若在此时又有未在其他地方被处理的 SystemExit 则仍将退出进程,而任何其他退出方式都将按如上文所述的方式设置返回值。

在记录于 运行时配置 一节的 CPython 运行时配置 API 文档中(不考虑错误处理),Py_Main 大致相当于:

  1. PyConfig config;
  2. PyConfig_InitPythonConfig(&config);
  3. PyConfig_SetArgv(&config, argc, argv);
  4. Py_InitializeFromConfig(&config);
  5. PyConfig_Clear(&config);
  6.  
  7. Py_RunMain();

在正常使用中,嵌入式应用程序将调用此函数 而不是 直接调用 Py_Initialize(), Py_InitializeEx()Py_InitializeFromConfig(),并且所有设置都将如本文档的其他部分所描述的那样被应用。 如果此函数改在某个先前的运行时初始化 API 调用 之后 被调用,那么到底那个环境和命令行配置会被更新将取决于具体的版本(因为它要依赖当运行时被初始化时究竟有哪些设置在它们已被设置一次之后是正确地支持被修改的)。

  • int Py_RunMain(void)
  • 在完整配置的 CPython 运行时中执行主模块。

执行在命令行或配置中指定的命令 (PyConfig.run_command)、脚本 (PyConfig.run_filename) 或模块 (PyConfig.run_module)。 如果这些值均未设置,则使用 __main__ 模块的全局命令空间来运行交互式 Python 提示符 (REPL)。

如果 PyConfig.inspect 未被设置(默认),则当解释器正常退出(即未引发异常)时返回值将为 0,或者如果解释器因异常而退出则返回 1。 如果引发了未在其他地方被处理的 SystemExit,此函数将立即退出进程而不会返回 1

如果 PyConfig.inspect 已被设置(例如当使用了 -i 选项时),则当解释器退出时,执行并不会返回而是会使用 __main__ 模块的全局命名空间在交互式 Python 提示符 (REPL) 中继续。 如果解释器因异常而退出,该异常将在 REPL 会话中被立即引发。 之后此函数的返回值将由 REPL 会话 的终结方式来决定:如果会话没有引发未处理的异常即终结则返回 0,引发了未处理的 SystemExit 则立即退出,而对于任何其他未处理异常则返回 1

此函数总是会最终化 Python 解释器而不考虑它是返回一个值还是由于有未处理的 SystemExit 异常而立即退出进程。

请参阅 Python 配置 查看一个使用 Py_RunMain() 在隔离模式下始终运行定制的 Python 的示例。

  • 这是 不稳定 API。它可在次发布版中不经警告地改变。

为目标解释器 interp 注册一个 atexit 回调。 这与 Py_AtExit() 类似,但它接受一个显式的解释器和用于回调的数据指针。

必须为 interp 持有 GIL

Added in version 3.13.

进程级参数

  • void Py_SetProgramName(const wchar_t *name)

如果要调用该函数,应当在首次调用 Py_Initialize() 之前调用它。 它将告诉解释器程序的 main() 函数的 argv[0] 参数的值(转换为宽字符)。 Py_GetPath() 和下面的某些其他函数会使用它在相对于解释器的位置上查找可执行文件的 Python 运行时库。 默认值是 'python'。 参数应当指向静态存储中的一个以零值结束的宽字符串,其内容在程序执行期间不会发生改变。 Python 解释器中的任何代码都不会改变该存储的内容。

使用 Py_DecodeLocale() 来解码字节串以得到一个 wchar_* 字符串。

自 3.11 版本弃用.

  • wchar_t *Py_GetProgramName()
  • 属于 稳定 ABI. 返回用 Py_SetProgramName() 设置的程序名称,或默认的名称。 返回的字符串指向静态存储;调用者不应修改其值。

此函数不应在 Py_Initialize() 之前被调用,否则将返回 NULL

在 3.10 版本发生变更: 现在如果它在 Py_Initialize() 之前被调用将返回 NULL

Deprecated since version 3.13, will be removed in version 3.15: 改为获取 sys.executable

  • wchar_t *Py_GetPrefix()
  • 属于 稳定 ABI. 返回针对已安装的独立于平台文件的 prefix。 这是通过基于使用 PyConfig.program_name 设置的程序名称和某些环境变量所派生的一系列复杂规则来获取的;举例来说,如果程序名称为 'usrlocal/bin/python',则 prefix 为 'usrlocal'。 返回的字符串将指向静态存储;调用方不应修改其值。 这对应于最高层级 Makefile 中的 prefix 变量以及在编译时传给 configure 脚本的 --prefix 参数。 该值将作为 sys.base_prefix 供 Python 代码使用。 它仅适用于 Unix。 另请参见下一个函数。

此函数不应在 Py_Initialize() 之前被调用,否则将返回 NULL

在 3.10 版本发生变更: 现在如果它在 Py_Initialize() 之前被调用将返回 NULL

Deprecated since version 3.13, will be removed in version 3.15: 改为获取 sys.base_prefix,或者如果需要处理 虚拟环境 则为获取 sys.prefix

  • wchar_t *Py_GetExecPrefix()
  • 属于 稳定 ABI. 返回针对已安装的 依赖于 平台文件的 exec-prefix。 这是通过基于使用 PyConfig.program_name 设置的程序名称和某些环境变量所派生的一系列复杂规则来获取的;举例来说,如果程序名称为 'usrlocal/bin/python',则 exec-prefix 为 'usrlocal'。 返回的字符串将指向静态存储;调用方不应修改其值。 这对应于最高层级 Makefile 中的 exec_prefix 变量以及在编译时传给 configure 脚本的 --exec-prefix 参数。 该值将作为 sys.base_exec_prefix 供 Python 代码使用。 它仅适用于 Unix。

背景:当依赖于平台的文件(如可执行文件和共享库)是安装于不同的目录树中的时候 exec-prefix 将会不同于 prefix。 在典型的安装中,依赖于平台的文件可能安装于 the usrlocal/plat 子目录树而独立于平台的文件可能安装于 usrlocal

总而言之,平台是一组硬件和软件资源的组合,例如所有运行 Solaris 2.x 操作系统的 Sparc 机器会被视为相同平台,但运行 Solaris 2.x 的 Intel 机器是另一种平台,而运行 Linux 的 Intel 机器又是另一种平台。 相同操作系统的不同主要发布版通常也会构成不同的平台。 非 Unix 操作系统的情况又有所不同;这类系统上的安装策略差别巨大因此 prefix 和 exec-prefix 是没有意义的,并将被设为空字符串。 请注意已编译的 Python 字节码是独立于平台的(但并不独立于它们编译时所使用的 Python 版本!)

系统管理员知道如何配置 mount 或 automount 程序以在平台间共享 usrlocal 而让 usrlocal/plat 成为针对不同平台的不同文件系统。

此函数不应在 Py_Initialize() 之前被调用,否则将返回 NULL

在 3.10 版本发生变更: 现在如果它在 Py_Initialize() 之前被调用将返回 NULL

Deprecated since version 3.13, will be removed in version 3.15: 改为获取 sys.base_exec_prefix,或者如果需要处理 虚拟环境 则为获取 sys.exec_prefix

  • wchar_t *Py_GetProgramFullPath()
  • 属于 稳定 ABI. 返回 Python 可执行文件的完整程序名称;这是作为基于程序名称(由 PyConfig.program_name 设置)派生默认模块搜索路径的附带影响计算得出的。 返回的字符串将指向静态存储;调用方不应修改其值。 该值将以 sys.executable 的名称供 Python 代码访问。

此函数不应在 Py_Initialize() 之前被调用,否则将返回 NULL

在 3.10 版本发生变更: 现在如果它在 Py_Initialize() 之前被调用将返回 NULL

Deprecated since version 3.13, will be removed in version 3.15: 改为获取 sys.executable

  • wchar_t *Py_GetPath()
  • 属于 稳定 ABI. 返回默认模块搜索路径;这是基于程序名称(由 PyConfig.program_name 设置)和某些环境变量计算得出的。 返回的字符串由一系列以依赖于平台的分隔符分开的目录名称组成。 此分隔符在 Unix 和 macOS 上为 ':',在 Windows 上为 ';'。 返回的字符串将指向静态存储;调用方不应修改其值。 列表 sys.path 将在解释器启动时使用该值来初始化;它可以在随后被修改(并且通常都会被修改)以变更用于加载模块的搜索路径。

此函数不应在 Py_Initialize() 之前被调用,否则将返回 NULL

在 3.10 版本发生变更: 现在如果它在 Py_Initialize() 之前被调用将返回 NULL

Deprecated since version 3.13, will be removed in version 3.15: 改为获取 sys.path

  • const char *Py_GetVersion()
  • 属于 稳定 ABI. 返回 Python 解释器的版本。 这将为如下形式的字符串
  1. "3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]"

第一个单词(到第一个空格符为止)是当前的 Python 版本;前面的字符是以点号分隔的主要和次要版本号。 返回的字符串将指向静态存储;调用方不应修改其值。 该值将以 sys.version 的名称供 Python 代码使用。

另请参阅 Py_Version 常量。

  • const char *Py_GetPlatform()
  • 属于 稳定 ABI. 返回当前平台的平台标识符。 在 Unix 上,这将以操作系统的“官方”名称为基础,转换为小写形式,再加上主版本号;例如,对于 Solaris 2.x,或称 SunOS 5.x,该值将为 'sunos5'。 在 macOS 上,它将为 'darwin'。 在 Windows 上它将为 'win'。 返回的字符串指向静态存储;调用方不应修改其值。 Python 代码可通过 sys.platform 获取该值。
  • const char *Py_GetCopyright()
  • 属于 稳定 ABI. 返回当前 Python 版本的官方版权字符串,例如

'Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam'

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

  • const char *Py_GetCompiler()
  • 属于 稳定 ABI. 返回用于编译当前 Python 版本的编译器指令,为带方括号的形式,例如:
  1. "[GCC 2.7.2.2]"

返回的字符串指向静态存储;调用者不应修改其值。 Python 代码可以从变量 sys.version 中获取该值。

  • const char *Py_GetBuildInfo()
  • 属于 稳定 ABI. 返回有关当前Python解释器实例的序列号和构建日期和时间的信息,例如:
  1. "#67, Aug 1 1997, 22:34:28"

返回的字符串指向静态存储;调用者不应修改其值。 Python 代码可以从变量 sys.version 中获取该值。

  • 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 为 0argv[0] 未指向现有文件名),则将在 sys.path 的开头添加一个空字符串,这等价于添加当前工作目录 (".")。

使用 Py_DecodeLocale() 来解码字节串以得到一个 wchar_* 字符串。

另请参阅 Python 初始化配置PyConfig.orig_argvPyConfig.argv 成员。

备注

建议在出于执行单个脚本以外的目的嵌入 Python 解释器的应用传入 0 作为 updatepath,并在需要时更新 sys.path 本身。 参见 CVE 2008-5983 [https://www.cve.org/CVERecord?id=CVE-2008-5983]。

在 3.1.3 之前的版本中,你可以通过在调用 PySys_SetArgv() 之后手动弹出第一个 sys.path 元素,例如使用:

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

Added in version 3.1.3.

自 3.11 版本弃用.

  • void PySys_SetArgv(int argc, wchar_t **argv)

此函数相当于 PySys_SetArgvEx() 设置了 updatepath 为 1 除非 python 解释器启动时附带了 -I

使用 Py_DecodeLocale() 来解码字节串以得到一个 wchar_* 字符串。

另请参阅 Python 初始化配置PyConfig.orig_argvPyConfig.argv 成员。

在 3.4 版本发生变更: updatepath 值依赖于 -I

自 3.11 版本弃用.

  • void Py_SetPythonHome(const wchar_t *home)

设置默认的 "home" 目录,也就是标准 Python 库所在的位置。 请参阅 PYTHONHOME 了解该参数字符串的含义。

此参数应当指向静态存储中一个以零值结束的字符串,其内容在程序执行期间将保持不变。 Python 解释器中的代码绝不会修改此存储中的内容。

使用 Py_DecodeLocale() 来解码字节串以得到一个 wchar_* 字符串。

自 3.11 版本弃用.

  • wchar_t *Py_GetPythonHome()
  • 属于 稳定 ABI. 返回默认的 "home",就是由 PyConfig.home 所设置的值,或者在设置了 PYTHONHOME 环境变量的情况下则为该变量的值。

此函数不应在 Py_Initialize() 之前被调用,否则将返回 NULL

在 3.10 版本发生变更: 现在如果它在 Py_Initialize() 之前被调用将返回 NULL

Deprecated since version 3.13, will be removed in version 3.15: 改为获取 PyConfig.homePYTHONHOME 环境变量。

线程状态和全局解释器锁

Python 解释器不是完全线程安全的。 为了支持多线程的 Python 程序,设置了一个全局锁,称为 global interpreter lockGIL,当前线程必须在持有它之后才能安全地访问 Python 对象。 如果没有这个锁,即使最简单的操作也可能在多线程的程序中导致问题:例如,当两个线程同时增加相同对象的引用计数时,引用计数可能最终只增加了一次而不是两次。

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

Python 解释器会在一个名为 PyThreadState 的数据结构体中保存一些线程专属的记录信息。 还有一个全局变量指向当前的 PyThreadState: 它可以使用 PyThreadState_Get() 来获取。

从扩展扩展代码中释放 GIL

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

  1. 将线程状态保存到一个局部变量中。
  2. 释放全局解释器锁。
  3. ... 执行某些阻塞式的 I/O 操作 ...
  4. 重新获取全局解释器锁。
  5. 从局部变量中恢复线程状态。

这是如此常用因此增加了一对宏来简化它:

  1. Py_BEGIN_ALLOW_THREADS
  2. ... 执行某些阻塞式的 I/O 操作 ...
  3. Py_END_ALLOW_THREADS

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

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

  1. PyThreadState *_save;
  2.  
  3. _save = PyEval_SaveThread();
  4. ... 执行某些阻塞式的 I/O 操作 ...
  5. 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 的典型方式如下:

  1. PyGILState_STATE gstate;
  2. gstate = PyGILState_Ensure();
  3.  
  4. /* 在此执行 Python 动作。 */
  5. result = CallSomeFunction();
  6. /* 评估结果或处理异常 */
  7.  
  8. /* 释放线程。 在此之后不再允许 Python API。 */
  9. PyGILState_Release(gstate);

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

有关 fork() 的注意事项

有关线程的另一个需要注意的重要问题是它们在面对 C fork() 调用时的行为。 在大多数支持 fork() 的系统中,当一个进程执行 fork 之后将只有发出 fork 的线程存在。 这对需要如何处理锁以及CPython 的运行时内所有的存储状态都会有实质性的影响。

只保留“当前”线程这一事实意味着任何由其他线程所持有的锁永远不会被释放。 Python 通过在 fork 之前获取内部使用的锁,并随后释放它们的方式为 os.fork() 解决了这个问题。 此外,它还会重置子进程中的任何 锁对象。 在扩展或嵌入 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 扩展代码或在嵌入 Python 解释器时最常用的类型和函数:

  • type PyInterpreterState
  • 属于 受限 API (作为不透明的结构体). 该数据结构代表多个合作线程所共享的状态。 属于同一解释器的线程将共享其模块管理以及其他一些内部条目。 该结构体中不包含公有成员。

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

  • type PyThreadState
  • 属于 受限 API (作为不透明的结构体). 该数据结构代表单个线程的状态。 唯一的公有数据成员为:

  • 该线程的解释器状态。
  • void PyEval_InitThreads()
  • 属于 稳定 ABI. 不执行任何操作的已弃用函数。

在 Python 3.6 及更老的版本中,此函数会在 GIL 不存在时创建它。

在 3.9 版本发生变更: 此函数现在不执行任何操作。

在 3.7 版本发生变更: 该函数现在由 Py_Initialize() 调用,因此你无需再自行调用它。

在 3.2 版本发生变更: 此函数已不再被允许在 Py_Initialize() 之前调用。

自 3.9 版本弃用.

  • 属于 稳定 ABI. 释放全局解释器锁 (如果已创建) 并将线程状态重置为 NULL,返回之前的线程状态 (不为 NULL)。 如果锁已被创建,则当前线程必须已获取到它。
  • 属于 稳定 ABI. 获取全局解释器锁 (如果已创建) 并将线程状态设为 tstate,它必须不为 NULL。 如果锁已被创建,则当前线程必须尚未获取它,否则将发生死锁。

备注

当运行时正在最终化时从某个线程调用此函数将终结该线程,即使线程不是由 Python 创建的。 你可以在调用此函数之前使用 Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否还处于最终化过程中以避免不必要的终结操作。

  • 属于 稳定 ABI. 返回当前线程状态。 全局解释器锁必须被持有。 在当前状态为 NULL 时,这将发出一个致命错误 (这样调用方将无须检查是否为 NULL)。

另请参阅 PyThreadState_GetUnchecked()

  • PyThreadState_Get() 类似,但如果其为 NULL 则不会杀死进程并设置致命错误。 调用方要负责检查结果是否为 NULL。

Added in version 3.13: 在 Python 3.5 到 3.12 中,此函数是私有的并且命名为 PyThreadStateUncheckedGet()

  • 属于 稳定 ABI. 交换当前线程状态与由参数 tstate (可能为 NULL) 给出的线程状态。 全局解释器锁必须被持有且未被释放。

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

  • PyGILState_STATE PyGILState_Ensure()

返回值是一个当 PyGILState_Ensure() 被调用时的线程状态的不透明“句柄”,并且必须被传递给 PyGILState_Release() 以确保 Python 处于相同状态。 虽然允许递归调用,但这些句柄 不能 被共享 —— 每次对 PyGILState_Ensure() 的单独调用都必须保存其对 PyGILState_Release() 的调用的句柄。

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

备注

当运行时正在最终化时从某个线程调用此函数将终结该线程,即使线程不是由 Python 创建的。 你可以在调用此函数之前使用 Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否还处于最终化过程中以避免不必要的终结操作。

  • void PyGILState_Release(PyGILState_STATE)
  • 属于 稳定 ABI. 释放之前获取的任何资源。 在此调用之后,Python 的状态将与其在对相应 PyGILState_Ensure() 调用之前的一样(但是通常此状态对调用方来说将是未知的,对 GILState API 的使用也是如此)。

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

  • 属于 稳定 ABI. 获取此线程的当前线程状态。 如果当前线程上没有使用过 GILState API 则可以返回 NULL。 请注意主线程总是会有这样一个线程状态,即使没有在主线程上执行过自动线程状态调用。 这主要是一个辅助/诊断函数。
  • int PyGILState_Check()
  • 如果当前线程持有 GIL 则返回 1 否则返回 0。 此函数可以随时从任何线程调用。 只有当它的 Python 线程状态已经初始化并且当前持有 GIL 时它才会返回 1。 这主要是一个辅助/诊断函数。 例如在回调上下文或内存分配函数中会很有用处,当知道 GIL 被锁定时可以允许调用方执行敏感的操作或是在其他情况下做出不同的行为。

Added in version 3.4.

以下的宏被使用时通常不带末尾分号;请在 Python 源代码发布包中查看示例用法。

  • Py_BEGIN_ALLOW_THREADS
  • 属于 稳定 ABI. 此宏会扩展为 { PyThreadState *_save; _save = PyEval_SaveThread();。 请注意它包含一个开头花括号;它必须与后面的 Py_END_ALLOW_THREADS 宏匹配。 有关此宏的进一步讨论请参阅上文。
  • Py_END_ALLOW_THREADS
  • 属于 稳定 ABI. 此宏扩展为 PyEval_RestoreThread(_save); }。 注意它包含一个右花括号;它必须与之前的 Py_BEGIN_ALLOW_THREADS 宏匹配。 请参阅上文以进一步讨论此宏。
  • Py_BLOCK_THREADS
  • Py_UNBLOCK_THREADS

底层级 API

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

在 3.7 版本发生变更: Py_Initialize() 现在会初始化 GIL

  • 属于 稳定 ABI. 创建一个新的解释器状态对象。 不需要持有全局解释器锁,但如果有必要序列化对此函数的调用则可能会持有。

引发一个不带参数的 审计事件 cpython.PyInterpreterState_New

  • 属于 稳定 ABI. 重置解释器状态对象中的所有信息。 必须持有全局解释器锁。

引发一个不带参数的 审计事件 cpython.PyInterpreterState_Clear

  • 属于 稳定 ABI. 创建属于给定解释器对象的新线程状态对象。全局解释器锁不需要保持,但如果需要序列化对此函数的调用,则可以保持。
  • 属于 稳定 ABI. 重置线程状态对象中的所有信息。 必须持有全局解释器锁。

在 3.9 版本发生变更: 此函数现在会调用 PyThreadState.on_delete 回调。 在之前版本中,此操作是发生在 PyThreadState_Delete() 中的。

在 3.13 版本发生变更: PyThreadState.on_delete 回调已被移除。

  • 属于 稳定 ABI. 销毁线程状态对象。 不需要持有全局解释器锁。 线程状态必须使用之前对 PyThreadState_Clear() 的调用来重置。
  • void PyThreadState_DeleteCurrent(void)
  • 属于 稳定 ABI 自 3.10 版起. 获取 Python 线程状态 tstate 的当前帧。

返回一个 strong reference。 如果没有当前执行的帧则返回 NULL

另请参阅 PyEval_GetFrame()

tstate 必须不为 NULL

Added in version 3.9.

  • 属于 稳定 ABI 自 3.10 版起. 获取 Python 线程状态 tstate 的唯一线程状态标识符。

tstate 必须不为 NULL

Added in version 3.9.

  • 属于 稳定 ABI 自 3.10 版起. 获取 Python 线程状态 tstate 对应的解释器。

tstate 必须不为 NULL

Added in version 3.9.

  • 暂停 Python 线程状态 tstate 中的追踪和性能分析。

使用 PyThreadState_LeaveTracing() 函数来恢复它们。

Added in version 3.11.

另请参阅 PyEval_SetTrace()PyEval_SetProfile() 函数。

Added in version 3.11.

  • 属于 稳定 ABI 自 3.9 版起. 获取当前解释器。

如果不存在当前 Python 线程状态或不存在当前解释器则将发出致命级错误信号。 它无法返回 NULL。

呼叫者必须持有GIL。

Added in version 3.9.

  • 属于 稳定 ABI 自 3.7 版起. 返回解释器的唯一 ID。 如果执行过程中发生任何错误则将返回 -1 并设置错误。

呼叫者必须持有GIL。

Added in version 3.7.

  • 属于 稳定 ABI 自 3.8 版起. 返回一个存储解释器专属数据的字典。 如果此函数返回 NULL 则没有任何异常被引发并且调用方应当将解释器专属字典视为不可用。

这不是 PyModule_GetState() 的替代,扩展仍应使用它来存储解释器专属的状态信息。

Added in version 3.8.

  • 这是 不稳定 API。它可在次发布版中不经警告地改变。

为给定的解释器返回一个指向 __main__ 模块对象strong reference

呼叫者必须持有GIL。

Added in version 3.13.

  • 帧评估函数的类型

throwflag 形参将由生成器的 throw() 方法来使用:如为非零值,则处理当前异常。

在 3.9 版本发生变更: 此函数现在可接受一个 tstate 形参。

在 3.11 版本发生变更: frame 形参由 PyFrameObject* 改为 _PyInterpreterFrame*

  • 获取帧评估函数。

请参阅 PEP 523 [https://peps.python.org/pep-0523/] "Adding a frame evaluation API to CPython"。

Added in version 3.9.

  • 设置帧评估函数。

请参阅 PEP 523 [https://peps.python.org/pep-0523/] "Adding a frame evaluation API to CPython"。

Added in version 3.9.

  • 返回值:借入的引用。 属于 稳定 ABI. 返回一个扩展可以在其中存储线程专属状态信息的字典。 每个扩展都应当使用一个独有的键用来在该字典中存储状态。 在没有可用的当前线程状态时也可以调用此函数。 如果此函数返回 NULL,则还没有任何异常被引发并且调用方应当假定没有可用的当前线程状态。
  • int PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
  • 属于 稳定 ABI. 在一个线程中异步地引发异常。 id 参数是目标线程的线程 id;exc 是要引发的异常对象。 该函数不会窃取任何对 exc 的引用。 为防止随意滥用,你必须编写你自己的 C 扩展来调用它。 调用时必须持有 GIL。 返回已修改的线程状态数量;该值通常为一,但如果未找到线程 id 则会返回 0。 如果 exc 为NULL,则会清除线程的待处理异常(如果存在)。 这将不会引发异常。

在 3.7 版本发生变更: id 形参的类型已从 long 变为 unsigned long。

  • 属于 稳定 ABI. 获取全局解释器锁并将当前线程状态设为 tstate,它必须不为 NULL。 锁必须在此之前已被创建。 如果该线程已获取锁,则会发生死锁。

备注

当运行时正在最终化时从某个线程调用此函数将终结该线程,即使线程不是由 Python 创建的。 你可以在调用此函数之前使用 Py_IsFinalizing()sys.is_finalizing() 来检查解释器是否还处于最终化过程中以避免不必要的终结操作。

在 3.8 版本发生变更: 已被更新为与 PyEval_RestoreThread(), Py_END_ALLOW_THREADS()PyGILState_Ensure() 保持一致,如果在解释器正在最终化时被调用则会终结当前线程。

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

  • 属于 稳定 ABI. 将当前线程状态重置为 NULL 并释放全局解释器锁。 在此之前锁必须已被创建并且必须由当前的线程所持有。 tstate 参数必须不为 NULL,该参数仅被用于检查它是否代表当前线程状态 —- 如果不是,则会报告一个致命级错误。

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

子解释器支持

虽然在大多数用例中,你都只会嵌入一个单独的 Python 解释器,但某些场景需要你在同一个进程甚至同一个线程中创建多个独立的解释器。 子解释器让你能够做到这一点。

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

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

  • type PyInterpreterConfig
  • 包含用于配置子解释器的大部分形参的结构体。 其值仅在 Py_NewInterpreterFromConfig() 中被使用而绝不会被运行时所修改。

Added in version 3.12.

结构体字段:

  • int use_main_obmalloc
  • 如果该值为 0 则子解释器将使用自己的“对象”分配器状态。 否则它将使用(共享)主解释器的状态。

如果该值为 0check_multi_interp_extensions 必须为 1 (非零值)。 如果该值为 1gil 不可为 PyInterpreterConfig_OWN_GIL

  • int allow_fork
  • 如果该值为 0 则运行时将不支持在当前激活了子解释器的任何线程中 fork 进程。 否则 fork 将不受限制。

请注意当 fork 被禁止时 subprocess 模块将仍然可用。

  • int allow_exec
  • 如果该值为 0 则运行时将不支持在当前激活了子解释器的任何线程中通过 exec (例如 os.execv()) 替换当前进程。 否则 exec 将不受限制。

请注意当 exec 被禁止时 subprocess 模块将仍然可用。

  • int allow_threads
  • 如果该值为 0 则子解释器的 threading 模块将不会创建线程。 否则线程将被允许。

  • int allow_daemon_threads

  • 如果该值为 0 则子解释器的 threading 模块将不会创建守护线程。 否则将允许守护线程(只要 allow_threads 是非零值)。

  • int check_multi_interp_extensions

  • 这将确定针对子解释器的 GIL 操作方式。 它可以是以下的几种之一:

    • PyInterpreterConfig_DEFAULT_GIL
  1. -

使用默认选择 (PyInterpreterConfig_SHARED_GIL)。

  1. - PyInterpreterConfig_SHARED_GIL
  2. -

使用(共享)主解释器的 GIL。

  1. - PyInterpreterConfig_OWN_GIL
  2. -

使用子解释器自己的 GIL。

如果该值为 PyInterpreterConfig_OWN_GILPyInterpreterConfig.use_main_obmalloc 必须为 0

  • 新建一个子解释器。 这是一个 (几乎) 完全隔离的 Python 代码执行环境。 特别需要注意,新的子解释器具有全部已导入模块的隔离的、独立的版本,包括基本模块 builtins, __main__sys 等。 已加载模块表 (sys.modules) 和模块搜索路径 (sys.path) 也是隔离的。 新环境没有 sys.argv 变量。 它具有新的标准 I/O 流文件对象 sys.stdin, sys.stdoutsys.stderr (不过这些对象都指向相同的底层文件描述符)。

给定的 config 控制着初始化解释器所使用的选项。

成功后,tstate_p 将被设为新的子解释器中创建的第一个线程状态。该线程状态是在当前线程状态中创建的。 请注意并没有真实的线程被创建;请参阅下文有关线程状态的讨论。 如果创建新的解释器没有成功,则 tstate_p 将被设为 NULL;不会设置任何异常因为异常状态是存储在当前的线程状态中而当前线程状态并不一定存在。

与所有其他 Python/C API 函数一样,在调用此函数之前必须先持有全局解释器锁并且在其返回时仍继续持有。 同样地在进入函数时也必须设置当前线程状态。 执行成功后,返回的线程状态将被设为当前线程状态。 如果创建的子解释器具有自己的 GIL 那么调用方解释器的 GIL 将被释放。 当此函数返回时,新的解释器的 GIL 将由当前线程持有而之前的解释器的 GIL 在此将保持释放状态。

Added in version 3.12.

子解释器在彼此相互隔离,并让特定功能受限的情况下是最有效率的:

  1. PyInterpreterConfig config = { .use_main_obmalloc = 0, .allow_fork = 0, .allow_exec = 0, .allow_threads = 1, .allow_daemon_threads = 0, .check_multi_interp_extensions = 1, .gil = PyInterpreterConfig_OWN_GIL,
  2. };
  3. PyThreadState *tstate = NULL;
  4. PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config);
  5. if (PyStatus_Exception(status)) { Py_ExitStatusException(status);
  6. }

请注意该配置只会被短暂使用而不会被修改。 在初始化期间配置的值会被转换成各种 PyInterpreterState 值。 配置的只读副本可以被内部存储于 PyInterpreterState 中。

扩展模块将以如下方式在(子)解释器之间共享:

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

  • 对于使用单阶段初始化的模块,例如 PyModule_Create(),当特定扩展被首次导入时,它将被正常初始化,并会保存其模块字典的一个 (浅) 拷贝。 当同一扩展被另一个 (子) 解释器导入时,将初始化一个新模块并填充该拷贝的内容;扩展的 init 函数不会被调用。 因此模块字典中的对象最终会被 (子) 解释器所共享,这可能会导致预期之外的行为 (参见下文的 Bugs and caveats)。

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

  • 属于 稳定 ABI. 新建一个子解释器。 这在本质上只是针对 Py_NewInterpreterFromConfig() 的包装器,其配置保留了现有的行为。 结果是一个未隔离的子解释器,它会共享主解释器的 GIL,允许 fork/exec,允许守护线程,也允许单阶段初始化模块。
  • 属于 稳定 ABI. 销毁由给定的线程状态所代表的(子)解释器。 给定的线程状态必须为当前的线程状态。 请参阅下文中关于线程状态的讨论。 当调用返回时,当前的线程状态将为 NULL。 与此解释器相关联的所有线程状态都会被销毁。 在调用此函数之前必须持有目标解释器所使用的全局解释器锁。 当其返回时将不再持有 GIL。

Py_FinalizeEx() 将销毁所有在当前时间点上尚未被明确销毁的子解释器。

解释器级 GIL

使用 Py_NewInterpreterFromConfig() 你将可以创建一个与其他解释器完全隔离的子解释器,包括具有自己的 GIL。 这种隔离带来的最大好处在于这样的解释器执行 Python 代码时不会被其他解释器所阻塞或者阻塞任何其他解释器。 因此在运行 Python 代码时单个 Python 进程可以真正地利用多个 CPU 核心。 这种隔离还能鼓励开发者采取不同于仅使用线程的并发方式。 (参见 PEP 554 [https://peps.python.org/pep-0554/])。

使用隔离的解释器要求谨慎地保持隔离状态。 尤其是意味着不要在未确保线程安全的情况下共享任何对象或可变的状态。 由于引用计数的存在即使是在其他情况下不可变的对象 (例如 None, (1, 5)) 通常也不可被共享。 针对此问题的一种简单但效率较低的解决方式是在使用某些状态 (或对象) 时总是使用一个全局锁。 或者,对象实际上不可变的对象 (如整数或字符串) 可以通过将其设为 immortal 对象而无视其引用计数来确保其安全性。 事实上,对于内置单例、小整数和其他一些内置对象都是这样做的。

如果你能保持隔离状态那么你将能获得真正的多核计算能力而不会遇到自由线程所带来的复杂性。 如果未能保持隔离状态那么你将面对自由线程所带来的全部后果,包括线程竞争和难以调试的崩溃。

除此之外,使用多个相互隔离的解释器的一个主要挑战是如何在它们之间安全 (不破坏隔离状态)、高效地进行通信。 运行时和标准库还没有为此提供任何标准方式。 未来的标准库模块将会帮助减少保持隔离状态所需的工作量并为解释器之间的数据通信(和共享)公开有效的工具。

Added in version 3.12.

错误和警告

由于子解释器 (以及主解释器) 都是同一个进程的组成部分,它们之间的隔离状态并非完美 —- 举例来说,使用低层级的文件操作如 os.close() 时它们可能 (无意或恶意地) 影响它们各自打开的文件。 由于 (子) 解释器之间共享扩展的方式,某些扩展可能无法正常工作;在使用单阶段初始化或者 (静态) 全局变量时尤其如此。 在一个子解释器中创建的对象有可能被插入到另一个 (子) 解释器的命名空间中;这种情况应当尽可能地避免。

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

还要注意的一点是将此功能与 PyGILState_* API 结合使用是很微妙的,因为这些 API 会假定 Python线程状态与操作系统级线程之间存在双向投影关系,而子解释器的存在打破了这一假定。 强烈建议你不要在一对互相匹配的 PyGILState_Ensure()PyGILState_Release() 调用之间切换子解释器。 此外,使用这些 API 以允许从非 Python 创建的线程调用 Python 代码的扩展 (如 ctypes) 在使用子解释器时很可能会出现问题。

异步通知

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

  • int Py_AddPendingCall(int (func)(void), void *arg)
  • 属于 稳定 ABI. 将一个函数加入从主解释器线程调用的计划任务。 成功时,将返回 0 并将 func 加入要被主线程调用的等待队列。 失败时,将返回 -1 但不会设置任何异常。

当成功加入队列后,func 将 最终 附带参数 arg 被主解释器线程调用。 对于正常运行的 Python 代码来说它将被异步地调用,但要同时满足以下两个条件:

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

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

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

警告

这是一个低层级函数,只在非常特殊的情况下有用。 不能保证 func 会尽快被调用。 如果主线程忙于执行某个系统调用,func 将不会在系统调用返回之前被调用。 此函数 通常 不适合 从任意 C 线程调用 Python 代码。 作为替代,请使用 PyGILStateAPI

Added in version 3.1.

在 3.9 版本发生变更: 如果此函数在子解释器中被调用,则函数 func 将被安排在子解释器中调用,而不是在主解释器中调用。现在每个子解释器都有自己的计划调用列表。

分析和跟踪

Python 解释器为附加的性能分析和执行跟踪工具提供了一些低层级的支持。 它们可被用于性能分析、调试和覆盖分析工具。

这个 C 接口允许性能分析或跟踪代码避免调用 Python 层级的可调用对象带来的开销,它能直接执行 C 函数调用。 此工具的基本属性没有变化;这个接口允许针对每个线程安装跟踪函数,并且向跟踪函数报告的基本事件与之前版本中向 Python 层级跟踪函数报告的事件相同。

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
  • 当一个行编号事件被报告时传给 Py_tracefunc 函数 (但不会传给性能分析函数) 的 what 形参的值。 它可以通过将 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 来显式地请求。
  • 将性能分析器函数设为 func。 obj 形参将作为第一个形参传给该函数,它可以是任意 Python 对象或为 NULL。 如果性能分析函数需要维护状态,则为每个线程的 obj 使用不同的值将提供一个方便而线程安全的存储位置。 这个性能分析函数将针对除 PyTrace_LINE PyTrace_OPCODEPyTrace_EXCEPTION 以外的所有被监控事件进行调用。

另请参阅 sys.setprofile() 函数。

调用方必须持有 GIL

  • 类似于 PyEval_SetProfile() 但会在属于当前解释器的所有在运行线程中设置性能分析函数而不是仅在当前线程上设置。

调用方必须持有 GIL

PyEval_SetProfile() 一样,该函数会忽略任何被引发的异常同时在所有线程中设置性能分析函数。

Added in version 3.12.

另请参阅 sys.settrace() 函数。

调用方必须持有 GIL

  • 类似于 PyEval_SetTrace() 但会在属于当前解释器的所有在运行线程中设置跟踪函数而不是仅在当前线程上设置。

调用方必须持有 GIL

PyEval_SetTrace() 一样,该函数会忽略任何被引发的异常同时在所有线程中设置跟踪函数。

Added in version 3.12.

引用追踪

Added in version 3.13.

  • typedef int (PyRefTracer)(PyObject, int event, void *data)

Added in version 3.13.

  • int PyRefTracer_CREATE
  • 当一个 Python 对象被创建时传给 PyRefTracer 函数的 event 形参。
  • int PyRefTracer_DESTROY
  • 当一个 Python 对象被销毁时传给 PyRefTracer 函数的 event 形参。
  • int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
  • 注册一个引用追踪函数。 该函数将在新的 Python 对象被创建或对象被销毁时被调用。 如果提供了 data 则它必须是一个当追踪函数被调用时所提供的不透明指针。 成功时返回 0。 发生错误时将设置一个异常并返回 -1

请注意该追踪函数 不可 在其内部创建 Python 对象否则调用将被重入。 该追踪器也 不可 清除任何现有异常或者设置异常。 每次当追踪器被调用时都将持有 GIL。

当调用此函数时必须持有 GIL。

Added in version 3.13.

  • 获取已注册的引用追踪函数以及当 PyRefTracer_SetTracer() 被调用时所注册的不透明数据指针的值。 如果未注册任何追踪器则此函数将返回 NULL 并将 data 指针设为 NULL。

当调用此函数时必须持有 GIL。

Added in version 3.13.

高级调试器支持

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

  • 将解释器状态对象返回到由所有此类对象组成的列表的开头。
  • 返回主解释器状态对象。
  • 从由解释器状态对象组成的列表中返回 interp 之后的下一项。
  • 在由与解释器 interp 相关联的线程组成的列表中返回指向第一个 PyThreadState 对象的指针。
  • 从由属于同一个 PyInterpreterState 对象的线程状态对象组成的列表中返回 tstate 之后的下一项。

线程本地存储支持

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

当调用这些函数时 无须 持有 GIL;它们会提供自己的锁机制。

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

备注

这些 API 函数都不会为 void 的值处理内存管理问题。 你需要自己分配和释放它们。 如果 void 值碰巧为 PyObject*,这些函数也不会对它们执行引用计数操作。

线程专属存储 (TSS) API

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

Added in version 3.7.

参见

"A New C-API for Thread-Local Storage in CPython" ( PEP 539 [https://peps.python.org/pep-0539/])

  • 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 编译的扩展模块中是必须的,在这些模块由于此类型的实现在编译时是不透明的因此它不可能静态分配。

  • 属于 稳定 ABI 自 3.7 版起. 返回一个与使用 Py_tss_NEEDS_INIT 初始化的值的状态相同的值,或者当动态分配失败时则返回 NULL
  • 属于 稳定 ABI 自 3.7 版起. 在首次调用 PyThread_tss_delete() 以确保任何相关联的线程局部变量已被撤销赋值之后释放由 PyThread_tss_alloc() 所分配的给定的 key。 如果 key 参数为 NULL 则这将无任何操作。

备注

被释放的 key 将变成一个悬空指针。 你应当将 key 重置为 NULL

方法

这些函数的形参 key 不可为 NULL。 并且,如果给定的 Py_tss_t 还未被 PyThread_tss_create() 初始化则 PyThread_tss_set()PyThread_tss_get() 的行为将是未定义的。

  • int PyThread_tss_is_created(Py_tss_t *key)
  • 属于 稳定 ABI 自 3.7 版起. 当成功初始化一个 TSS 键时将返回零值。 如果 key 参数所指向的值未被 Py_tss_NEEDS_INIT 初始化则其行为是未定义的。 此函数可在相同的键上重复调用 — 在已初始化的键上调用它将不执行任何操作并立即成功返回。
  • void PyThread_tss_delete(Py_tss_t *key)
  • 属于 稳定 ABI 自 3.7 版起. 销毁一个 TSS 键以便在所有线程中遗忘与该键相关联的值,并将该键的初始化状态改为未初始化的。 已销毁的键可以通过 PyThread_tss_create() 再次被初始化。 此函数可以在同一个键上重复调用 — 但在一个已被销毁的键上调用将是无效的。
  • int PyThread_tss_set(Py_tss_t key, void value)
  • 属于 稳定 ABI 自 3.7 版起. 返回零值来表示成功将一个 void 值与当前线程中的 TSS 键相关联。 每个线程都有一个从键到 void 值的独立映射。
  • 属于 稳定 ABI 自 3.7 版起. 返回当前线程中与一个 TSS 键相关联的 void* 值。 如果当前线程中没有与该键相关联的值则返回 NULL

线程本地存储 (TLS) API

自 3.7 版本弃用: 此 API 已被 线程专属存储 (TSS) API 所取代。

备注

这个 API 版本不支持原生 TLS 键采用无法被安全转换为 int 的的定义方式的平台。 在这样的平台上,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()

同步原语

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

  • type PyMutex
  • 一个互斥锁。 PyMutex 应当被初始化为零以代表未加锁状态。 例如:
  1. PyMutex mutex = {0};

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

备注

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

Added in version 3.13.

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

Added in version 3.13.

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

Added in version 3.13.

Python 关键节 API

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

关键节机制通过在调用 PyEval_SaveThread() 期间隐式地挂起活动的关键节并释放锁来避免死锁。 当 PyEval_RestoreThread() 被调用时,最近的关键节将被恢复,并重新获取它的锁。 这意味着关键节 API 提供了与传统锁相比更弱的保证 — 它们有用是因为它们的行为与 GIL 类似。

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

备注

需要同时锁定两个对象的操作必须使用 Py_BEGIN_CRITICAL_SECTION2。 你 不可 使用嵌套的关键节来同时锁定一个以上的对象,因为内层的关键节可能会挂起外层的关键节。 这个 API 没有提供同时锁定两个以上对象的办法。

用法示例:

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

在上面的例子中,Py_SETREF 调用了 Py_DECREF,它可以通过一个对象的取消分配函数来调用任意代码。 当由最终化器触发的代码发生阻塞并调用 PyEval_SaveThread() 时关键节 API 将通过允许运行临时挂起关键节来避免由于重入和锁顺序导致的潜在死锁。

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

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

  1. { PyCriticalSection pycs; PyCriticalSection_Begin(&pycs, (PyObject*)(op))

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

Added in version 3.13.

  • Py_END_CRITICAL_SECTION()
  • 结束关键节并释放每对象锁。

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

  1. PyCriticalSection_End(&pycs);
  2. }

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

Added in version 3.13.

  • Py_BEGIN_CRITICAL_SECTION2(a, b)
  • 为对象 a 和 b 获取每对象锁并开始一个关键节。 这些锁是按连续顺序获取的(最低的地址在最前)以避免锁顺序列死锁。

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

  1. { PyCriticalSection2 pycs2; PyCriticalSection2_Begin(&pycs2, (PyObject*)(a), (PyObject*)(b))

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

Added in version 3.13.

  • Py_END_CRITICAL_SECTION2()
  • 结束关键节并释放每对象锁。

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

  1. PyCriticalSection2_End(&pycs2);
  2. }

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

Added in version 3.13.