【问题标题】:Python GUI event dispatch thread blocks when other worker thread uses C++ code当其他工作线程使用 C++ 代码时 Python GUI 事件调度线程阻塞
【发布时间】:2013-11-12 08:59:57
【问题描述】:

我创建了一个简单的 GUI 应用程序,并希望在工作线程而不是事件调度线程上运行长时间的操作,以保持 GUI 响应。

只要工作线程正在运行 python 函数,它就可以正常工作。但是,如果该函数在 C++ 代码上执行某些操作,则 GUI 线程会阻塞,就好像在它而不是在工作线程上执行了延长的操作一样!

看起来好像有一些隐藏的锁阻止 GUI 线程与工作线程并行运行。

我怀疑它可能与正在使用的 UI 框架有关,所以我尝试了 TKinter 和 wxPython 都出现了问题。

我在 Windows 7 上使用 Visual Studio 2010 和 python 2.7.5

这是 C++ 代码:

注意:我也尝试过使用 Sleep(timeSec * 1000L) 进行非忙等待,具有相同的行为

#include<boost/python.hpp>
#include <ctime>

void busyWait(int timeSec) {
    clock_t beginTime(clock()); 
    while (clock() - beginTime < timeSec * 1000L);
}

using namespace boost::python;

BOOST_PYTHON_MODULE(BusyCpp) {
    def("busyWait", &busyWait, "waits...");
}

这就是python代码

# Set the path to append the DLL to it
from Utils import ToolEnvironment
ToolEnvironment.useToolBinaries()

from threading import Thread
import Tkinter
import BusyCpp


class simpleapp_tk(Tkinter.Tk):

    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)

        self.waitPeriod = 5  # seconds

        button1 = Tkinter.Button(self, text=u"Busy C++", command=self.OnBusyCppClick)
        button1.place(x=20, y=20)

        button2 = Tkinter.Button(self, text=u"Busy Python", command=self.OnBusyPyClick)
        button2.place(x=20, y=60)

    def OnBusyCppClick(self):
        t = Thread(target=self.busyWaitCpp)
        t.start()
        print 'Started thread'

    def OnBusyPyClick(self):
        t = Thread(target=self.busyWaitPy)
        t.start()
        print 'Started thread'

   def busyWaitCpp(self):
        BusyCpp.busyWait(self.waitPeriod)
        print 'Done waiting C++'

   def busyWaitPy(self):
        from time import time
        beginTime = time()
        while time() - beginTime < self.waitPeriod:
            pass
        print 'Done waiting python'

if __name__ == "__main__":
    app = simpleapp_tk(None)
    app.title('my application')
    app.mainloop()

单击“Busy Python”按钮时,可以看到 GUI 正在响应(按钮看起来未单击),并且这些打印输出按此顺序显示,其中“已启动线程”立即出现,正如预期的那样:

Started thread
Done waiting python

当单击“Busy C++”按钮时,可以看到 GUI 没有响应(按钮在等待时看起来像被单击)并且这些打印输出按此顺序显示,其中两者仅在等待结束时出现:

Done waiting C++
Started thread

所以很明显,只有在工作线程完成工作后,GUI 线程才能打印“已启动线程”

知道如何解决这个线程问题吗?

谢谢

【问题讨论】:

    标签: python user-interface boost-python event-dispatch-thread worker


    【解决方案1】:

    在从 Python 调用的扩展代码中执行长时间运行的计算时,您需要 release the GIL

    您可以使用配对的宏Py_BEGIN_ALLOW_THREADSPy_BEGIN_ALLOW_THREADS,或使用范围管理器(例如from here):

    class ScopedGILRelease {
    public:
        inline ScopedGILRelease() { m_thread_state = PyEval_SaveThread(); }
        inline ~ScopedGILRelease() {
            PyEval_RestoreThread(m_thread_state);
            m_thread_state = NULL;
        }
    private:
        PyThreadState* m_thread_state;
    };
    

    【讨论】:

    • 这很有帮助。将 ScopedGILRelease 实例添加到我的 C++ 函数后,一切都像魅力一样工作。
    • @YalonLotan:这个answer 提供了一个解决方案,可以将busyWait() 与必须了解Python 解耦。如果您在 Python 之外运行调用 busyWait() 的 C++ 线程,则可能值得考虑。
    【解决方案2】:

    看起来好像有一些隐藏的锁”它并没有隐藏得那么好,它被称为全局解释器锁(GIL),任何在 Python 中进行多线程处理的人都应该知道它。 https://wiki.python.org/moin/GlobalInterpreterLock

    C++ 代码可以自己获取和释放锁。见http://docs.python.org/2/c-api/init.html

    【讨论】:

    • 谢谢,它确实解释了这一切。我使用了@ectamur ScopedGILRelease,它起作用了。
    猜你喜欢
    • 1970-01-01
    • 2018-12-20
    • 2013-07-11
    • 1970-01-01
    • 2013-03-30
    • 2016-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多