自由线程的 C API 扩展支持
*************************

从 3.13 发布起，CPython 通过 *free threading* 配置项引入了禁用 *global
interpreter lock* (GIL) 的实验性支持。这份文档阐述了如何修改 C API 扩
展以支持自由线程。


在 C 中识别自由线程构建
=======================

CPython C API 提供了 "Py_GIL_DISABLED" 宏，它在自由线程构建中被定义为
"1"，而在常规构建中未被定义。你可以使用它让代码仅在自由线程构建中运行
：

   #ifdef Py_GIL_DISABLED
   /* 仅在自由线程构建版中运行的代码 */
   #endif


模块初始化
==========

扩展模块需要明确指明它们支持在禁用 GIL 的情况下运行；否则导入扩展模块
时会引发警告，并在运行时启用 GIL。

取决于扩展使用多阶段还是单阶段初始化，有两种方式指明扩展模块支持在 GIL
禁用的情况下运行。


多阶段初始化
------------

使用多阶段初始化（例如 "PyModuleDef_Init()"）的扩展应该在模块定义中添
加 "Py_mod_gil" 槽位。如果你的扩展需要支持更老版本的 CPython，请检查
"PY_VERSION_HEX" 以保护槽位。

   static struct PyModuleDef_Slot module_slots[] = {
       ...
   #if PY_VERSION_HEX >= 0x030D0000
       {Py_mod_gil, Py_MOD_GIL_NOT_USED},
   #endif
       {0, NULL}
   };

   static struct PyModuleDef moduledef = {
       PyModuleDef_HEAD_INIT,
       .m_slots = module_slots,
       ...
   };


单阶段初始化
------------

使用单阶段初始化（即 "PyModule_Create()"）的扩展应该调用
"PyUnstable_Module_SetGIL()" 来表明它们支持在禁用 GIL 的情况下运行。该
函数只在自由线程构建中被定义，因此应使用 "#ifdef Py_GIL_DISABLED" 来保
护调用，以避免在常规构建中出现编译错误。

   static struct PyModuleDef moduledef = {
       PyModuleDef_HEAD_INIT,
       ...
   };

   PyMODINIT_FUNC
   PyInit_mymodule(void)
   {
       PyObject *m = PyModule_Create(&moduledef);
       if (m == NULL) {
           return NULL;
       }
   #ifdef Py_GIL_DISABLED
       PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
   #endif
       return m;
   }


通用 API 指南
=============

大多数 C API 是线程安全的，但是也存在例外。

* **结构字段**：如果 Python C API 对象或结构的字段可能被并行修改，那么
  直接访问这些字段不是线程安全的。

* **宏**："PyList_GET_ITEM" 以及 "PyList_SET_ITEM" 等访问宏不进行错误
  检查和上锁，因而容器对象可能被并行修改时它们不是线程安全的。

* **借入引用**：返回 *借入引用* 的  C API 函数如果引用内容可能被并行修
  改，那么它不是线程安全的。详见 借入引用。


容器相关的线程安全
------------------

"PyListObject", "PyDictObject" 及 "PySetObject" 等容器在自由线程构建中
执行内部上锁机制，例如 "PyList_Append()" 在追加对象前会对列表上锁。


"PyDict_Next"
~~~~~~~~~~~~~

一个值得注意的例外是 "PyDict_Next()"，它不会锁定目录。 在迭代目录时如
果该目录可能被并发地修改那么你应当使用 "Py_BEGIN_CRITICAL_SECTION" 来
保护它:

   Py_BEGIN_CRITICAL_SECTION(dict);
   PyObject *key, *value;
   Py_ssize_t pos = 0;
   while (PyDict_Next(dict, &pos, &key, &value)) {
       ...
   }
   Py_END_CRITICAL_SECTION();


借入引用
========

有些 C API 函数返回 *borrowed references*。如果引用内容可能被并行修改
，那么这些 API 不是线程安全的。例如，如果列表可能被并行修改，那么使用
"PyList_GetItem()" 是不安全的。

下表列出了一些返回借入引用的 API 及它们返回 *强引用* 的替代版本。

+-------------------------------------+-------------------------------------+
| 借入引用 API                        | 强引用 API                          |
|=====================================|=====================================|
| "PyList_GetItem()"                  | "PyList_GetItemRef()"               |
+-------------------------------------+-------------------------------------+
| "PyDict_GetItem()"                  | "PyDict_GetItemRef()"               |
+-------------------------------------+-------------------------------------+
| "PyDict_GetItemWithError()"         | "PyDict_GetItemRef()"               |
+-------------------------------------+-------------------------------------+
| "PyDict_GetItemString()"            | "PyDict_GetItemStringRef()"         |
+-------------------------------------+-------------------------------------+
| "PyDict_SetDefault()"               | "PyDict_SetDefaultRef()"            |
+-------------------------------------+-------------------------------------+
| "PyDict_Next()"                     | 无 (参见 PyDict_Next)               |
+-------------------------------------+-------------------------------------+
| "PyWeakref_GetObject()"             | "PyWeakref_GetRef()"                |
+-------------------------------------+-------------------------------------+
| "PyWeakref_GET_OBJECT()"            | "PyWeakref_GetRef()"                |
+-------------------------------------+-------------------------------------+
| "PyImport_AddModule()"              | "PyImport_AddModuleRef()"           |
+-------------------------------------+-------------------------------------+

返回借用引用的 API 不一定都有问题。例如，"PyTuple_GetItem()" 是安全的
，因为元组是不可变的。同样，上述 API 的使用不一定都有问题。 例如，
"PyDict_GetItem()" 通常用于解析函数调用中的关键字参数字典；这些关键字
参数字典实际上是私有（其他线程无法访问）的，因此在这种情况下使用借入引
用是安全的。

上述函数中有的是在 Python 3.13 中添加的。在旧 Python 版本上您可以使用
提供这些函数实现的 pythoncapi-compat 包。


内存分配 API
============

Python 的内存管理 C API 提供了三个不同 分配域 的函数: "raw", "mem" 和
"object"。 为了保证线程安全，自由线程构建版要求只有 Python 对象使用
object 域来分配，并且所有 Python 对象都应使用该域来分配。 这不同于之前
的 Python 版本，因为在此之前这只是一个最佳实践而不是硬性要求。

备注:

  搜索 "PyObject_Malloc()" 在您的扩展中的使用，并检查分配的内存是否用
  于 Python 对象。使用 "PyMem_Malloc()" 来分配缓冲区，而不是
  "PyObject_Malloc()"。


线程状态与 GIL API
==================

Python 提供了一系列函数和宏来管理线程状态和 GIL，例如：

* "PyGILState_Ensure()" 与 "PyGILState_Release()"

* "PyEval_SaveThread()" 与 "PyEval_RestoreThread()"

* "Py_BEGIN_ALLOW_THREADS" 与 "Py_END_ALLOW_THREADS"

即使 *GIL* 被禁用，仍应在自由线程构建中使用这些函数管理线程状态。例如
，如果在 Python 之外创建线程，则必须在调用 Python API 前调用
"PyGILState_Ensure()"，以确保线程具有有效的 Python 线程状态。

你应该继续在阻塞操作（如输入/输出或获取锁）前调用
"PyEval_SaveThread()" 或 "Py_BEGIN_ALLOW_THREADS"，以允许其他线程运行
*循环垃圾回收器*。


保护内部扩展状态
================

您的扩展可能有以前受 GIL 保护的内部状态。您可能需要上锁来保护内部状态
。具体方法取决于您的扩展，但一些常见的模式包括：

* **缓存**：全局缓存是共享状态的常见来源。如果缓存对性能并不重要，可考
  虑使用锁来保护缓存，或在自由线程构建中禁用缓存。

* **全局状态**：全局状态可能需要用锁保护或移至线程本地存储。C11 和
  C++11 提供了 "thread_local" 或 "_Thread_local" 用于 线程本地存储。


为自由线程构建进行扩展构建
==========================

C API 扩展需要专门为自由线程构建进行构建。构建的 wheel、共享库和二进制
文件用后缀 "t" 指示。

* pypa/manylinux 支持后缀为 "t" 的自由线程构建，如 "python3.13t"。

* 如果设置了 CIBW_FREE_THREADED_SUPPORT，则 pypa/cibuildwheel 支持自由
  线程构建。


受限的 C API 与稳定 ABI
-----------------------

自由线程构建目前不支持 受限 C API 或稳定 ABI。 如果当前您使用
setuptools 来构建您的扩展，并且设置了 "py_limited_api=True"，您可以使
用 "py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")" 在
使用自由线程构建进行构建时不使用受限 API。

备注:

  您需要为自由线程构建单独构建 wheel。如果您当前使用稳定 ABI，则可以继
  续构建适用于多个非自由线程 Python 版本的单个 wheel。


Windows
-------

由于 Windows 官方安装程序的限制，从源代码构建扩展时需要手动定义
"Py_GIL_DISABLED=1"。

参见:

  Porting Extension Modules to Support Free-Threading: 一份由社区维护
  的针对扩展开发者的移植指南。
