PySide 中的多线程提升 Python C++ 代码
Posted
技术标签:
【中文标题】PySide 中的多线程提升 Python C++ 代码【英文标题】:Multithreading Boost Python C++ Code in PySide 【发布时间】:2012-08-16 16:01:11 【问题描述】:我有用 Boost Python 封装的 c++ 代码。这个想法是,我创建了一个共享对象,我的 python GUI 可以使用它来实例化包装 c++ 功能的变量
c++ 代码做了一些繁重的工作,我希望能够使包装的对象同时运行,这样 GUI 就不会阻塞。
我编译了一个用 CMake 包装的 Boost Python 共享对象,如下所示:
find_package(Boost COMPONENTS system thread python REQUIRED)
find_package(PythonLibs REQUIRED)
include_directories($Boost_INCLUDE_DIRS)
include_directories($PYTHON_INCLUDE_DIRS)
link_directories($Boost_LIBRARY_DIRS)
link_directories($PYTHON_LIBRARIES)
set(Boost_USE_MULTITHREADED ON)
add_library(mynewlibinpython SHARED src/boost_python_wrapper_code.cpp)
target_link_libraries(mynewlibinpython $Boost_LIBRARIES $PYTHON_LIBRARIES mylibincpp)
我可以很好地在 python 中实例化对象,但它会导致 GUI 阻塞。这是我的 python 代码的通用 sn-p:
from mynewlibinpython import *
class CppWrapper(QtCore.QObject):
def __init__(self):
super(CppWrapper,self).__init__()
#some more initialization...
@QtCore.Slot()
def heavy_lifting_cpp_invocation():
#constructor that takes a long time to complete
self.cpp_object = CppObject()
并且在具有成员函数的对象中,当我在 GUI 中按下按钮时会触发该函数:
class GuiObjectWithUnimportantName:
def __init__(self):
#inititalization
self.cpp_thread = QThread()
self.cpp_wrapper = CppWrapper()
def function_triggered_by_button_press(self):
self.cpp_wrapper.moveToThread(self.cpp_thread)
self.cpp_thread.started.connect(self.cpp_wrapper.heavy_lifting_cpp_invocation)
print "starting thread"
self.cpp_thread.start()
print "Thread started"
因此,cpp 代码将开始执行它的工作,而且我还将基本上立即从两个 print
语句(“启动线程”和“线程启动”)中获得输出。
但是图形用户界面被冻结了。这与python GIL有什么关系吗?我尝试将这些宏添加到 boost_python_wrapper_code.cpp
并重新编译,但它会导致 python GUI 在启动时立即出现段错误。
【问题讨论】:
【参考方案1】:是的,我相信您的问题与 GIL 有关。下面是一个可能发生的简单示例:
from PyQt4 import QtCore, QtGui
import time
class Window(QtGui.QDialog):
def __init__(self):
super(Window, self).__init__()
self.resize(200,100)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(QtGui.QLineEdit())
self.button1 = QtGui.QPushButton("Sleep")
self.button1.clicked.connect(self.go_safe)
layout.addWidget(self.button1)
self.button2 = QtGui.QPushButton("Crunch")
self.button2.clicked.connect(self.go_GIL_buster)
layout.addWidget(self.button2)
def go_safe(self):
t = QtCore.QThread(self)
heavy = Heavy()
heavy.moveToThread(t)
t.started.connect(heavy.safe)
print "starting thread"
t.start()
print "thread started"
self.t1 = t
self.heavy1 = heavy
def go_GIL_buster(self):
t = QtCore.QThread(self)
heavy = Heavy()
heavy.moveToThread(t)
t.started.connect(heavy.GIL_buster)
print "starting thread"
t.start()
print "thread started"
self.t2 = t
self.heavy2 = heavy
class Heavy(QtCore.QObject):
def safe(self):
print "Sleeping"
time.sleep(10)
print "Done"
def GIL_buster(self):
print "Crunching numbers"
x = 2
y = 500000000
foo = x**y
print "Done"
if __name__ == "__main__":
app = QtGui.QApplication([])
win = Window()
win.show()
win.raise_()
app.exec_()
当您单击第一个按钮时,线程将休眠 10 秒,您会注意到您仍然可以在文本字段中输入内容。睡眠会释放 GIL,因此主线程可以继续工作。
当您单击第二个按钮时,线程会执行大量数字操作。这会一直获取 GIL,并且 GUI 将锁定直到完成。
如果你的 boost 扩展没有以任何方式对 python 对象做任何事情,我认为你可以释放 GIL 以用于它的操作。然后它不会阻止GUI。如果你对 python 对象进行繁重的操作,那么你可能会遇到问题。
有一个tip on creating a scope-based GIL releasing class,您可以在任何函数中使用它来在该函数的持续时间内释放 GIL(当帮助程序实例被销毁时)
class ScopedGILRelease
// C & D ----------------------------
public:
inline ScopedGILRelease()
m_thread_state = PyEval_SaveThread();
inline ~ScopedGILRelease()
PyEval_RestoreThread(m_thread_state);
m_thread_state = NULL;
private:
PyThreadState * m_thread_state;
;
然后使用它...
int foo_wrapper(int x)
ScopedGILRelease scoped;
return foo(x);
其他人在此 repo 中有更多使用此技巧的示例:https://bitbucket.org/wwitzel3/code/src/tip/nogil/nogil.cpp
【讨论】:
好的,所以如果我理解正确,您的解决方案涉及在 .cpp 包装源或 .cpp 原始源周围使用Py_BEGIN_ALLOW_THREADS
和 Py_END_ALLOW_THREADS
(如在繁重的功能周围)实施)?
是的,无论公约如何促进获取和释放 gil。只要代码不修改 python 对象,您就可以释放该函数周围的 gil。
感谢 GIL 发布类的链接。完美运行。以上是关于PySide 中的多线程提升 Python C++ 代码的主要内容,如果未能解决你的问题,请参考以下文章