QThread 管理任务

QThread managment tasks

当我最多只有 4 个线程时,qthread 如何管理正在处理的线程数? 例如说我有 100 个长时间处理任务。当我 for 循环我的 100 个树视图项目并发出一个 Qthread 来处理每个任务时会发生什么?它会自动确定我只有 4 个线程并处理 4 个可用线程中的 100 个线程吗?

很好奇。 谢谢

import os
import sys
import time
import random
from PySide2 import QtCore, QtGui, QtWidgets


class TaskThread(QtCore.QThread):
    
    finished = QtCore.Signal(object)

    def __init__(self, index):
        super(TaskThread, self).__init__()        
        self._index = index

    def runTask(self, task):
        self._task = task

    def run(self):
        t = random.randint(0,4)
        time.sleep(t)
        self.finished.emit(self._index)


class ExampleDialog(QtWidgets.QDialog):

    def __init__(self):
        super(ExampleDialog, self).__init__()
        
        self.itemModel = QtGui.QStandardItemModel()

        self.uiListView = QtWidgets.QListView()
        self.uiListView.setModel(self.itemModel)

        self.mainLayout = QtWidgets.QVBoxLayout(self)
        self.mainLayout.addWidget(self.uiListView)

        self.populateItems()


    def populateItems(self):
        self.threads = []
        for x in range(100):
            output = 'C:/Users/JokerMartini-Asus/Desktop/Trash/thumbs/IMG_409{}.jpg'.format(x)

            # Item
            item = QtGui.QStandardItem('{}'.format(x))
            item.setData(QtGui.QPixmap(), QtCore.Qt.DecorationRole)
            item.setData(output, QtCore.Qt.UserRole)
            self.itemModel.appendRow(item)

            mIndex = QtCore.QPersistentModelIndex(self.itemModel.indexFromItem(item))
            tt = TaskThread(mIndex)
            
            self.threads.append(tt)
            tt.start()
            tt.finished.connect(self.processFinished)


    def processFinished(self, index):
        if index.isValid():
            model = index.model()
            model.setData(index, '{} updated'.format(index.row()), QtCore.Qt.DisplayRole)


if __name__ == '__main__':
    pass
    app = QtWidgets.QApplication(sys.argv)
    window = ExampleDialog()
    window.resize(300,600)
    window.show()
    window.raise_()
    sys.exit(app.exec_())

注意:我的回答仅基于经验实验和线程的基本知识。我对线程的底层实现没有足够的知识来提供我所表达内容的可靠资源,我也不能证明这在 Qt 支持的所有平台上都以相同的方式有效(我在 Linux).

在处理线程时,即使使用 Qt 绑定,也要考虑两件重要的事情:

  1. 线程不是关于“加速”事情,而是允许计算在其他人暂时空闲时进行处理;
  2. GIL 只允许每个进程一次 一个 操作,我们感知到明显的“并行性”的事实是由于每个线程都暂时释放控制到另一个;

从根本上讲,没有绝对的顺序,在python中线程没有严格的优先级(由于上述原因):理论上[几乎]按照线程启动的顺序进行处理,但是结果可能会有所不同,具体取决于每个线程的处理速度有多“快”,即使对于相同的操作也是如此。一旦一个线程释放对 GIL 的控制,“下一个”线程就会得到它。

通过对代码添加以下更改,您可能会看到稍微更可靠的结果:

class TaskThread(QtCore.QThread):
    # ...
    def run(self):
        # always use the same timeout
        time.sleep(1)
        self.finished.emit(self._index)


class ExampleDialog(QtWidgets.QDialog):
    # ...
    def processFinished(self, index):
        while app.hasPendingEvents():
            app.processEvents()
            time.sleep(.05)
        self.uiListView.repaint()
        # ...

注意:上面的代码会阻塞UI,hasPendingEvents已经过时了,正是由于线程的并发性,不鼓励使用processEvents为了同样的原因;考虑仅用于教育目的。

这意味着三件事:

  • 一般来说,python 中并发线程没有“限制”(因为线程不是并发的);
  • 重型计算在 python 中实际上并不与线程并行;
  • 最重要的是,在谈论线程时,“核心”的数量完全没有意义(尤其是在 python 中):在相同的情况下,2 核的结果相同 CPU 8 核 CPU;

真正的并行只能通过多进程实现,但问题是进程与线程不同,不共享内存。这并不意味着它完全不可能:this link 声称他们为 Qt signal/slot 机制实现了适当的多处理支持,但我从未尝试过,我不能说它确实有效(尤其是考虑到 link 已经很老了)。

注意:你不应该在线程启动后连接信号,特别是如果线程可能return立即( time.sleep(0));将 tt.finished.connect 行移动到 之前 tt.start().