Python C API -它是线程安全的吗?

kuuvgm7e  于 2023-04-29  发布在  Python
关注(0)|答案(2)|浏览(102)

我有一个C扩展,从我的多线程Python应用程序调用。我在C函数中的某个地方使用了一个静态变量i,稍后我有一些i++语句可以从不同的Python线程运行(该变量仅在我的C代码中使用,但我不会将其交给Python)。
由于某种原因,到目前为止我还没有遇到任何比赛条件,但我想知道这是否只是运气。..
我没有任何与线程相关的C代码(没有Py_开始_ALLOW_THREADS或任何东西)。
我知道GIL只保证单个字节码指令是原子的和线程安全的,因此Python中的i+=1语句不是线程安全的。
但是我不知道C扩展中的i++指令。有什么帮助吗?

bihw5rsg

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对象的竞争条件仍然可用。例如:

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
8wtpewkr

8wtpewkr2#

Python C扩展模式有一个很好的部分涵盖了Thread Safety
是的,这是一个老帖子,但它出现在我的谷歌搜索第一个,所以这个答案可能对其他人有用。

相关问题