gpt4 book ai didi

python - PySide/PyQt - 启动 CPU 密集型线程会挂起整个应用程序

转载 作者:太空狗 更新时间:2023-10-29 17:32:09 25 4
gpt4 key购买 nike

我正在尝试在我的 PySide GUI 应用程序中做一件相当常见的事情:我想将一些 CPU 密集型任务委托(delegate)给后台线程,以便我的 GUI 保持响应,甚至可以在计算进行时显示进度指示器。

这是我正在做的(我在 Python 2.7、Linux x86_64 上使用 PySide 1.1.1):

import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot

class Worker(QObject):
done_signal = Signal()

def __init__(self, parent = None):
QObject.__init__(self, parent)

@Slot()
def do_stuff(self):
print "[thread %x] computation started" % self.thread().currentThreadId()
for i in range(30):
# time.sleep(0.2)
x = 1000000
y = 100**x
print "[thread %x] computation ended" % self.thread().currentThreadId()
self.done_signal.emit()


class Example(QWidget):

def __init__(self):
super(Example, self).__init__()

self.initUI()

self.work_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.work_thread)
self.work_thread.started.connect(self.worker.do_stuff)
self.worker.done_signal.connect(self.work_done)

def initUI(self):

self.btn = QPushButton('Do stuff', self)
self.btn.resize(self.btn.sizeHint())
self.btn.move(50, 50)
self.btn.clicked.connect(self.execute_thread)

self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Test')
self.show()


def execute_thread(self):
self.btn.setEnabled(False)
self.btn.setText('Waiting...')
self.work_thread.start()
print "[main %x] started" % (self.thread().currentThreadId())

def work_done(self):
self.btn.setText('Do stuff')
self.btn.setEnabled(True)
self.work_thread.exit()
print "[main %x] ended" % (self.thread().currentThreadId())


def main():

app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())


if __name__ == '__main__':
main()

应用程序显示一个带有按钮的窗口。当按下按钮时,我希望它在执行计算时自行禁用。然后,应该重新启用该按钮。

相反,当我按下按钮时,整个窗口在计算进行时卡住,然后,当它完成时,我重新获得对应用程序的控制。该按钮似乎从未被禁用。我注意到的一件有趣的事情是,如果我用简单的 time.sleep() 替换 do_stuff() 中的 CPU 密集型计算,程序会按预期运行。

我不太清楚发生了什么,但看起来第二个线程的优先级太高了,以至于它实际上阻止了 GUI 线程的调度。如果第二个线程进入 BLOCKED 状态(就像 sleep() 发生的那样),GUI 实际上有机会按预期运行和更新界面。我尝试更改工作线程优先级,但在 Linux 上似乎无法完成。

此外,我尝试打印线程 ID,但不确定是否正确。如果我是,线程关联似乎是正确的。

我还用 PyQt 尝试了该程序,行为完全相同,因此有标签和标题。如果我能让它使用 PyQt4 而不是 PySide 运行,我可以将我的整个应用程序切换到 PyQt4

最佳答案

这可能是工作线程持有Python的GIL造成的。在某些 Python 实现中,一次只能执行一个 Python 线程。 GIL 阻止其他线程执行 Python 代码,并在不需要 GIL 的函数调用期间释放。

例如,GIL 在实际 IO 期间释放,因为 IO 由操作系统而不是 Python 解释器处理。

解决方案:

  1. 显然,您可以在您的工作线程中使用 time.sleep(0) 来让步给其他线程 (according to this SO question)。您必须自己定期调用 time.sleep(0),并且 GUI 线程只会在后台线程调用此函数时运行。

  2. 如果工作线程足够独立,您可以将其放入一个完全独立的进程中,然后通过管道发送已腌制的对象进行通信。在前台进程中,创建一个工作线程与后台进程做IO。由于工作线程将执行 IO 而不是 CPU 操作,它不会保留 GIL,这将为您提供一个完全响应的 GUI 线程。

  3. 一些 Python 实现(JPython 和 IronPython)没有 GIL。

CPython 中的线程 仅对多路复用 IO 操作真正有用,而不是将 CPU 密集型任务置于后台。对于许多应用程序,CPython 实现中的线程已从根本上被破坏,并且在可预见的 future 很可能会保持这种状态。

关于python - PySide/PyQt - 启动 CPU 密集型线程会挂起整个应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11265812/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com