Den*_*er9 5 c multithreading cpython python-c-api python-2.7
我有一个从我的多线程 Python 应用程序调用的 C 扩展。我i在 C 函数的某处使用了一个静态变量,i++稍后我有一些语句可以从不同的 Python 线程运行(尽管该变量仅在我的 C 代码中使用,但我不会将其交给 Python)。
出于某种原因,到目前为止我还没有遇到任何比赛条件,但我想知道这是否只是运气......
我没有任何与线程相关的 C 代码(没有 Py_BEGIN_ALLOW_THREADS 或任何东西)。
我知道 GIL 只保证单个字节码指令是原子和线程安全的,因此i+=1Python 中的语句不是线程安全的。
但我不知道i++C 扩展中的指令。有什么帮助吗?
当您运行 C 代码时,Python 不会释放 GIL(除非您告诉它或导致 Python 代码执行 -请参阅底部的警告注释!)。它仅在字节码指令之前(而不是期间)释放 GIL,并且从解释器的角度来看,运行 C 函数是执行字节码的一部分CALL_FUNCTION。* (不幸的是,我目前找不到该段落的参考资料,但我几乎可以肯定它是正确的)
因此,除非您执行任何特定操作,否则您的 C 代码将是唯一运行的线程,因此您在其中执行的任何操作都应该是线程安全的。
如果你特别想释放 GIL - 例如因为你正在做一个不干扰 Python 的长时间计算、读取文件或在等待其他事情发生时睡觉 - 那么最简单的方法就是这样Py_BEGIN_ALLOW_THREADS做Py_END_ALLOW_THREADS当你想拿回来的时候。在此块中,您无法使用大多数 Python API 函数,并且您有责任确保 C 中的线程安全。最简单的方法是仅使用局部变量,而不读取或写入任何全局状态。
如果您已经有一个没有 GIL 的 C 线程(线程 A)运行,那么仅仅将 GIL 保留在线程 B 中并不能保证线程 A 不会修改 C 全局变量。为了安全起见,您需要确保在所有 C 函数中如果没有某种锁定机制(Python GIL 或 C 机制),您永远不会修改全局状态。
额外的想法
*可以在 C 代码中释放 GIL 的地方之一是 C 代码调用导致 Python 代码执行的内容。这可能是通过使用PyObject_Call. 一个不太明显的地方是 ifPy_DECREF导致析构函数被执行。当 C 代码恢复时,您将恢复 GIL,但您无法再保证全局对象不变。这显然不会影响简单的 C 之类的x++。
需要强调的是,它真的非常非常容易导致Python代码的执行。因此,您不应该使用 GIL 来代替互斥体或实际的锁定机制。您应该只考虑真正原子的操作(即单个 C API 调用)或完全针对非 Python C 对象的操作。执行 C 代码时,您不会意外丢失 GIL,但许多 C API 调用可能会释放 GIL,执行其他操作,然后在返回 C 代码之前重新获得 GIL。
GIL 的目的是确保 Python 内部不会被损坏。GIL 将继续在扩展模块中实现此目的。然而,涉及以您不期望的方式排列的有效 Python 对象的竞争条件仍然可供您使用。例如:
PySequence_SetItem(some_list, 0, some_item);
PyObject* item = PySequence_GetItem(some_list, 0);
assert(item == some_item); // may not be true
// the destructor of the previous contents of item 0 may have released the GIL
Run Code Online (Sandbox Code Playgroud)