首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >PyQt QThreads中的同步活动

PyQt QThreads中的同步活动
EN

Stack Overflow用户
提问于 2014-01-08 19:02:14
回答 1查看 3.9K关注 0票数 2

我在和PyQt和QThreads一起玩。如果我使用我在这条蟒蛇小提琴中放置的代码(请注意,顶部部分是来自QtDesigner的自动生成的代码),其中循环的当前值在从线程中的循环中和控制进度条的循环中都打印出来,那么循环保持同步,在程序运行时所有点的值匹配,进度条准确地显示完成的从线程的比例。

作为对下面注释的回应,这个程序在当前状态下实际上完成了我想要它做的事情--它只是打印出,到终端,从线程中循环的值,以及控制进度条的循环中的值。

但是,注释掉第121行(也就是说,如果不在进度条循环中打印当前值),当从线程循环只达到~130次迭代(即进度条完成大约100%的速度)时,进度条将达到100% (即完成300次迭代)。

我做了什么愚蠢/错误的天真的事情吗?有更好的方法来完成我想做的事情吗?!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-01-10 10:16:57

仅仅因为有两个循环在不同的线程中执行相同的迭代次数,并不意味着它们将花费相同的时间来完成。通常,每次迭代的内容将花费一定的时间来完成,而且由于您的循环所做的事情不同,它们(通常)会占用不同的时间。

此外,使用Python中的线程,全局解释器锁(GIL)阻止线程在多核CPU上同时运行。GIL负责在线程之间切换并继续执行,然后切换到另一个线程,然后切换到另一个线程,等等。使用QThreads,这变得更加复杂,因为QThread中的Qt调用可以在没有GIL的情况下运行(据我理解),但是一般的python代码仍将与GIL一起运行。

因为GIL负责处理在任何给定时间运行的线程,我甚至看到两个线程在不同的速度下执行完全相同的操作。本身,这是一个完全的巧合,你的两个线程曾经在同一时间完成!

注意,由于GIL,两个cpu实例任务在单独的线程中运行没有速度上的好处。为此,您需要使用多处理。但是,如果您想将I/O绑定的任务(例如主线程中的GUI用户界面、另一个线程中的网络通信,也就是经常花费大量时间等待程序之外的东西触发的任务),那么线程是有用的。

因此,希望这有助于解释线程,以及在您的程序中发生了什么。

有几种方法可以更好地做到这一点。一种是将循环保留在线程中,但移除另一条。然后使用qt信号/插槽机制在MainWindow中调用一个函数,该函数运行过去存在的循环的一次迭代。但是,这并不保证同步,只是您的QThread将首先完成(某些东西可能会减慢主线程,从而导致事件堆积,而MainWindow中的函数直到稍后才会运行)。要完成同步,可以使用threading.Event对象使QThread等待,直到MainWindow中的新函数运行为止。

示例(未经测试,不好意思,但希望给出的想法!):

代码语言:javascript
复制
import threading
#==========================================
class TaskThread(QtCore.QThread):

    setTime = QtCore.pyqtSignal(int,int)
    iteration = QtCore.pyqtSignal(threading.Event, int)

    def run(self):

        self.setTime.emit(0,300)
        for i in range(300):
            time.sleep(0.05)
            event = threading.Event()
            self.iteration.emit(event, i)
            event.wait()

#==========================================
class MainWindow(QtGui.QMainWindow):

    _uiform = None

    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self,parent)
        self._uiform = Ui_MainWindow()
        self._uiform.setupUi(self)
        self._uiform.runButton.clicked.connect(self.startThread)


    def startThread(self):
        self._uiform.progressBar.setRange(0,0)
        self.task = TaskThread()
        self.task.setTime.connect(self.changePB)
        self.task.iteration.connect(self.update_prog_bar)
        self.task.start()

    @QtCore.pyqtSlot(int,int)
    def changePB(self, c, t):
        self.proportionFinished = int(math.floor(100*(float(c)/t)))
        self._uiform.progressBar.setValue(self.proportionFinished)

        self._uiform.progressBar.setRange(0,300)
        self._uiform.progressBar.setValue(0)

    @QtCore.pyqtSlot(threading._Event,int)
    def update_prog_bar(self,event, i)
        self._uiform.progressBar.setValue(i+1)
        print i
        event.set()

注意,使用@QtCore.pyqtSlot()装饰器是因为这里文档中的问题。简而言之,当您使用signal.connect(my_function)时,您忽略了确定插槽行为的第二个参数(无论是在调用signal.emit()时立即执行它,还是一旦控件返回到事件循环(也就是放置在队列中以便稍后运行)时执行它)。默认情况下,要连接的可选参数尝试自动决定要建立哪种类型的连接(请参阅这里)。但是,如果连接是在它知道它是线程之间的连接之前建立的,并且“槽”是**not* *被显式定义为使用@pyqtSlot的插槽,那么pyQT会感到困惑!

有关装潢器的附加信息:使用装饰器最简单的方式是将函数包装在另一个函数中。装饰器用自己的函数替换定义的函数,这个新函数通常在某个时候使用原始函数。因此,在@pyqtSlot的情况下,信号发射实际上调用了@pyqtSlot生成的pyqt函数,这个函数最终调用了您编写的原始函数。事实上,@pyqtSlot装饰器接受与插槽参数类型匹配的参数,这是pyqt装饰器的实现细节,而不是一般装饰器的代表。它只是简单地声明,您的插槽期望通过一个连接的信号传递指定类型的数据。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21004298

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档