Python C API - 线程安全吗?

时间:2017-02-02 15:38:50

标签: c multithreading python-2.7 python-c-api cpython

我有一个从我的多线程Python应用程序调用的C扩展。我在C函数的某个地方使用了静态变量i,稍后我会有几个i++语句可以从不同的Python线程运行(该变量仅用于我的C代码,但是我不要把它交给Python)。

出于某种原因,到目前为止我还没有遇到任何竞争条件,但我想知道它是否只是运气......

我没有任何线程相关的C代码(没有Py_BEGIN_ALLOW_THREADS或任何东西)。

我知道GIL只保证单字节码指令是原子的和线程安全的,因此Python中的i+=1语句不是线程安全的。

但我不知道C扩展中的i++指令。有什么帮助吗?

1 个答案:

答案 0 :(得分:3)

当您运行C代码时,Python不会释放GIL(除非您告诉它)。它仅在字节码指令之前(而不是在期间)释放GIL,并且从解释器的角度来看,运行C函数是执行CALL_FUNCTION字节码的一部分。* (很遗憾,我目前无法找到此段落的参考,但我几乎可以肯定它是正确的)

因此,除非您执行任何特定操作,否则您的C代码将是唯一运行的线程,因此您在其中执行的任何操作都应该是线程安全的。

如果您特别想要发布GIL - 例如,因为您正在进行长时间计算而不会干扰Python,从文件中读取或在等待其他事情发生时睡觉 - 那么最简单的方法是Py_BEGIN_ALLOW_THREADS then Py_END_ALLOW_THREADS when you want to get it back。在此块期间,您无法使用大多数Python API函数,并且您有责任确保C中的线程安全。最简单的方法是仅使用局部变量而不读取或写入任何全局状态。

如果您已经在没有GIL(线程A)的情况下运行了C线程,那么简单地在线程B中保持GIL并不能保证线程A不会修改C全局变量。为了安全起见,您需要确保在所有C函数中没有某种锁定机制(Python GIL或C机制)的情况下永远不会修改全局状态。

其他想法

*如果C代码调用导致Python代码执行的内容,那么可以在C代码中释放GIL的地方。这可能是通过使用PyObject_Call。如果Py_DECREF导致析构函数被执行,那么一个不太明显的地方就是如此。在C代码恢复之前,您已经恢复了GIL,但您无法再保证全局对象不变。这显然不会像x++那样影响简单的C。