Синхронизация активности в PyQt QThreads

Я играю с PyQt и QThreads. Кажется, что если я использую код, который я поместил в эту скрипту Python (обратите внимание, что верхний раздел представляет собой автоматически сгенерированный код из QtDesigner), где текущее значение цикла печатается как в цикле в подчиненном потоке, так и в цикл контролирует индикатор выполнения, затем циклы синхронизируются, значения совпадают во всех точках во время выполнения программы, а индикатор выполнения точно отображает долю завершенного подчиненного потока.

В ответ на комментарий ниже, эта программа в своем текущем состоянии фактически делает то, что я хочу - она ​​просто выводит на терминал значение цикла в подчиненном потоке и значение в цикле, которое контролирует прогрессию. индикатора выполнения.

Однако, комментируя строку 121 (т. Е. Если вы не печатаете текущее значение в цикле индикатора выполнения), результат выполнения индикатора достигает 100% (т.е. завершается 300 итераций), когда цикл подчиненного потока достиг только ~130 итераций (т.е. индикатор выполнения завершается примерно на 100% быстрее).

Я сделал что-то наивное глупо / неправильно - есть ли лучший способ завершить то, что я хочу сделать?!

1 ответ

Решение

То, что у вас два цикла, выполняющих одинаковое количество итераций в разных потоках, не означает, что для их завершения потребуется одинаковое время. Как правило, содержимое каждой итерации занимает некоторое время, и, поскольку ваши циклы выполняют разные задачи, они (как правило) будут занимать разные промежутки времени.

Более того, благодаря потокам в Python, Global-Interpreter-Lock (GIL) блокирует одновременную работу потоков на многоядерном процессоре. GIL отвечает за переключение между потоками и продолжение их выполнения перед переключением на другой, а затем на другой и т. Д. И т. Д. С QThreads это становится еще более сложным, поскольку вызовы Qt в QThread могут выполняться без GIL (поэтому я понимаю) но общий код Python все равно будет работать с GIL.

Поскольку GIL отвечает за обработку потока, выполняемого в любой момент времени, я даже видел, как два потока, выполняющие абсолютно одинаковые действия, работают с разными скоростями. Таким образом, это полное совпадение, что ваши два потока когда-либо заканчивались одновременно!,

Обратите внимание, что из-за GIL две интуитивно понятные задачи не получат преимущества от скорости при работе в отдельных потоках. Для этого вам нужно использовать мульти-обработку. Однако, если вы хотите выполнять задачи, связанные с вводом / выводом (например, пользовательский интерфейс через графический интерфейс пользователя в главном потоке, сетевое взаимодействие в другом потоке, то есть задачи, которые часто тратят много времени на ожидание чего-то вне программы) триггер что-то) тогда нить полезна.

Так что, надеюсь, это поможет объяснить многопоточность и то, что происходит в вашей программе.

Есть несколько способов сделать это лучше. Одним из них будет сохранить цикл в вашей теме, но удалить другой. Затем используйте механизм сигнала / слота 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() Декоратор из-за проблемы, задокументированной здесь. Короче говоря, когда вы используете signal.connect(my_function), вы опускаете второй аргумент, который определяет поведение слота (выполняется ли он сразу, когда signal.emit() вызывается или выполняется ли это, когда управление возвращается в цикл обработки событий (иначе говоря, помещается в очередь для последующего запуска)). По умолчанию этот необязательный аргумент для подключения пытается автоматически решить, какой тип подключения установить (см. Здесь), который обычно работает. Однако, если соединение установлено до того, как он узнает, что это соединение между потоками, и "слот" ** не * явно * определен как слот с использованием @pyqtSlot, PyQT запутался!

Дополнительная информация о декораторах. Самый простой способ представить декораторы - это сокращение для переноса функции в другую функцию. Декоратор заменяет вашу определенную функцию своей собственной, и в какой-то момент эта новая функция обычно использует вашу исходную функцию. Так что в @pyqtSlot В этом случае излучение сигнала фактически вызывает функцию pyqt, генерируемую @pyqtSlot и эта функция в конечном итоге вызывает оригинальную функцию, которую вы написали. Тот факт, что @pyqtSlot decorator принимает аргументы, которые соответствуют типам аргументов вашего слота, является деталью реализации декоратора pyqt и не является представителем декораторов в целом. Это просто говорит о том, что ваш слот ожидает передачи данных указанных типов по подключенному сигналу.

Другие вопросы по тегам