仅仅因为您有两个循环在不同的线程中执行相同次数的迭代,并不意味着它们将花费相同的时间来完成。通常,每次迭代的内容都需要一些时间才能完成,而且由于您的循环正在做不同的事情,它们(通常)会花费不同的时间。
此外,对于 Python 中的线程,全局解释器锁 (GIL) 可阻止线程同时在多核 CPU 上运行。 GIL 负责在线程之间切换,并继续它们的执行,然后再切换到另一个,然后是另一个,等等。使用 QThreads,这变得更加复杂,因为 QThread 中的 Qt 调用可以在没有 GIL 的情况下运行(所以我理解),但一般的 Python 代码仍将使用 GIL 运行。
因为 GIL 负责在任何给定时间处理正在运行的线程,所以我什至见过两个线程,做完全相同的事情,以不同的速度运行。 因此,您的两个线程同时完成完全是巧合!。
请注意,由于 GIL,两个 CPU 密集型任务不会因在单独的线程中运行而提高速度。为此,您需要使用多处理。但是,如果您想分出 I/O 绑定任务(例如通过主线程中的 GUI 进行用户界面,另一个线程中的网络通信,也就是通常花费大量时间等待程序外部的某些内容的任务)触发某些东西)然后线程很有用。
所以,希望这有助于解释线程,以及您的程序中发生了什么。
有几种方法可以更好地做到这一点。一种是将循环保留在您的线程中,但删除另一个。然后使用 qt 信号/槽机制调用MainWindow 中的函数,该函数运行曾经存在的循环的一次迭代。但这并不能保证同步,只是您的 QThread 将首先完成(某些事情可能会减慢主线程的速度,从而导致事件堆积,MainWindow 中的函数直到稍后才会运行)。要完成同步,您可以使用threading.Event 对象让QThread 等待MainWindow 中的新函数运行。
示例(未经测试,抱歉,但希望能给出想法!):
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() 装饰器的使用是因为here 记录的问题。简而言之,当您使用signal.connect(my_function) 时,您忽略了决定插槽行为的第二个参数(无论是在调用signal.emit() 时立即执行,还是在控制返回事件循环后执行(又名,放置在稍后运行的队列中))。默认情况下,connect 的这个可选参数会尝试自动决定建立哪种类型的连接(请参阅here),这通常会起作用。但是,如果在知道它是线程之间的连接之前就建立了连接,并且“插槽”**没有*使用@pyqtSlot明确定义为插槽,那么pyQT会感到困惑!
有关装饰器的其他信息:考虑装饰器的最简单方法是将函数包装在另一个函数中的简写。装饰器用它自己的函数替换你定义的函数,这个新函数通常在某个时候使用你原来的函数。所以在@pyqtSlot的情况下,信号发射实际上调用了@pyqtSlot生成的pyqt函数,而这个函数最终调用了你写的原始函数。 @pyqtSlot 装饰器采用与插槽参数类型匹配的参数这一事实是 pyqt 装饰器的实现细节,通常不代表装饰器。它只是说明您的插槽期望通过连接信号传递指定类型的数据。