我有一个C扩展,从我的多线程Python应用程序调用。我在C函数中的某个地方使用了一个静态变量i
,稍后我有一些i++
语句可以从不同的Python线程运行(该变量仅在我的C代码中使用,但我不会将其交给Python)。
由于某种原因,到目前为止我还没有遇到任何比赛条件,但我想知道这是否只是运气。..
我没有任何与线程相关的C代码(没有Py_开始_ALLOW_THREADS或任何东西)。
我知道GIL只保证单个字节码指令是原子的和线程安全的,因此Python中的i+=1
语句不是线程安全的。
但是我不知道C扩展中的i++
指令。有什么帮助吗?
2条答案
按热度按时间bihw5rsg1#
当你运行C代码时,Python不会释放GIL(除非你告诉它或导致Python代码的执行-请参阅底部的警告注解!))。它只在字节码指令之前释放GIL(而不是在执行过程中),从解释器的Angular 来看,运行C函数是执行
CALL_FUNCTION
字节码的一部分。*
(不幸的是,我目前找不到这段文字的参考资料,但我几乎可以肯定它是正确的)因此,除非你做任何特定的事情,否则你的C代码将是唯一运行的线程,因此你在其中做的任何操作都应该是线程安全的。
如果你特别想释放GIL --例如,因为你正在做一个不干扰Python的长时间计算,从文件中阅读,或者在等待其他事情发生的时候睡觉--那么最简单的方法是在你想要恢复GIL的时候先执行
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
。一个不太明显的地方是,如果Py_DECREF
导致析构函数被执行。当C代码恢复时,GIL已经恢复了,但不能再保证全局对象不变。这显然不会影响像x++
这样的简单C。后期编辑:
应该强调的是,导致Python代码的执行真的、真的、真的很容易。出于这个原因,您不应该使用GIL来代替互斥锁或实际的锁定机制。你应该只考虑那些真正的原子操作(即例如,单个C API调用)或完全在非Python C对象上。在执行C代码时,您不会意外地丢失GIL,但是许多C API调用可能会释放GIL,执行其他操作,然后在返回到您的C代码之前重新获得GIL。
GIL的目的是确保Python内部不会被破坏。GIL将在扩展模块中继续服务于此目的。但是,涉及以您不期望的方式排列的有效Python对象的竞争条件仍然可用。例如:
8wtpewkr2#
Python C扩展模式有一个很好的部分涵盖了Thread Safety。
是的,这是一个老帖子,但它出现在我的谷歌搜索第一个,所以这个答案可能对其他人有用。