PySide/Cython 与 GIL 多线程使用

3 投票
1 回答
1892 浏览
提问于 2025-04-17 13:55

我这几天一直在为一个PySide/Cython的多线程应用程序苦恼。
把问题拆分开来后,我进行了一些测试,使用了valgrind和helgrind(下面有一个错误的例子)。
这个C语言的函数(下面的load)在使用Python时涉及到全局解释器锁(GIL),而且没有给Cython下指令,Shiboken也在访问GIL。
输出结果显示,GIL的访问可能存在冲突(除非我理解错了),但我认为GIL的存在是为了避免这种冲突。

Shiboken在进行一些内存释放时,与另一个线程中的PyList_New发生了冲突……

我有点迷茫,
我以为GIL是用来阻止在一个线程修改一些Python共享数据时,PyList_New的调用的。难道Shiboken忘记上锁了吗?

listobject.c的第161行是调用_PyObject_GC_TRACK(),我猜这是在请求垃圾回收器(GC)跟踪这个新对象。
classobject.c的第2360行是调用_PyObject_GC_UNTRACK(),看起来像是在请求GC停止跟踪一个对象……
我看到helgrind的诊断是可能的数据竞争,但实际上这导致了核心转储,我不喜欢在使用线程时,Python的GC上出现“可能”这个词。我想先解决这个问题。

==26535== ----------------------------------------------------------------
==26535== 
==26535== Possible data race during read of size 8 at 0x4FE8488 by thread #2
==26535== Locks held: none
==26535==    at 0x4C92A80: PyList_New (listobject.c:161)
==26535==    by 0x742C43F: s2p_parseAndReadHDF (SIDStoPython.c:949)
==26535==    by 0x742C5C4: s2p_parseAndReadHDF (SIDStoPython.c:968)
==26535==    by 0x742E638: s2p_loadAsHDF (SIDStoPython.c:1485)
==26535==    by 0x741C3CC: __pyx_f_6CHLone_load (pyCHLone.c:2182)
==26535==    by 0x741D2AD: __pyx_pf_6CHLone_12load (pyCHLone.c:2422)
==26535==    by 0x741D1C3: __pyx_pw_6CHLone_13load (pyCHLone.c:2392)
==26535==    by 0x4D0A48F: PyEval_EvalFrameEx (ceval.c:4013)
==26535==    by 0x4D0C3DC: PyEval_EvalCodeEx (ceval.c:3253)
==26535==    by 0x4C8B641: function_call (funcobject.c:526)
==26535==    by 0x4C5F652: PyObject_Call (abstract.c:2529)
==26535==    by 0x4C7279E: instancemethod_call (classobject.c:2578)
==26535== 
==26535== This conflicts with a previous write of size 8 by thread #1
==26535== Locks held: none
==26535==    at 0x4C6C53F: instancemethod_dealloc (classobject.c:2360)
==26535==    by 0x5AB248A: Shiboken::AutoDecRef::~AutoDecRef() (in /tmp/tools-2/local/x86z/lib/python2.7/site-packages/PySide/QtCore.so)
==26535==    by 0x5F9736F: PySide::GlobalReceiverV2::qt_metacall(QMetaObject::Call, int, void**) (in /tmp/tools-2/local/x86z/lib/libpyside-python2.7.so.1.0.9)
==26535==    by 0x659BCA5: QObject::event(QEvent*) (in /tmp/tools-2/local/x86z/lib/libQtCore.so.4.8.0)
==26535==    by 0x5B038C5: QCoreApplicationWrapper::notify(QObject*, QEvent*) (in /tmp/tools-2/local/x86z/lib/python2.7/site-packages/PySide/QtCore.so)
==26535==    by 0x6586F8B: QCoreApplication::notifyInternal(QObject*, QEvent*) (in /tmp/tools-2/local/x86z/lib/libQtCore.so.4.8.0)
==26535==    by 0x658A5A7: QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) (in /tmp/tools-2/local/x86z/lib/libQtCore.so.4.8.0)
==26535==    by 0x65B60F2: ??? (in /tmp/tools-2/local/x86z/lib/libQtCore.so.4.8.0)
==26535==

问题:
1. 我对GIL的理解是正确的吗?
2. 如果
…这是否意味着我需要自己在每个线程中管理GIL锁?
…那我是不是得为Python创建自己的互斥锁?!??!?
3. 如果
…那我的Cython或C库在使用GIL时是否存在问题?
…这会不会是QThread/QMutex的副作用?

1 个回答

3

在CPython中,全局解释器锁(GIL)是一种互斥锁,它防止多个本地线程同时执行Python字节码。

所以,你可以理解GIL的作用就是让两段Python代码不能同时运行。除此之外,你不应该期待它有其他的锁定功能。依赖GIL来保护你的代码通常是不好的做法。很多人都希望能去掉这个GIL,因为在一些Python的版本,比如IronPython和Jython中是没有这个锁的。在有GIL的地方,它会降低并发性,增加额外的开销,让Python代码的理解变得更加困难。

从上面的内容来看,我并不清楚是否涉及到GIL的失败。第二个跟踪信息似乎是在C++的深层次,而不是在我理解的GIL限制的范围内。

撰写回答