#python #pyqt5 #python-multithreading #qthread #qrunnable
Вопрос:
Я пытаюсь понять, почему этот Worker
поток, который намеренно использует довольно интенсивную обработку (в частности, сортировку этих словарей), приводит к тому, что поток графического интерфейса перестает отвечать. Вот MRE:
from PyQt5 import QtCore, QtWidgets
import sys, time, datetime, random
def time_print(msg):
ms_now = datetime.datetime.now().isoformat(sep=' ', timespec='milliseconds')
thread = QtCore.QThread.currentThread()
print(f'{thread}, {ms_now}: {msg}')
def dict_reorder(dictionary):
return {k: v for k, v in sorted(dictionary.items())}
class Sequence(object):
n_sequence = 0
simple_sequence_map = {}
sequence_to_sequence_map = {}
prev_seq = None
def __init__(self):
Sequence.n_sequence = 1
if Sequence.n_sequence % 1000 == 0:
print(f'created sequence {Sequence.n_sequence}')
rand_int = random.randrange(100000)
self.text = str(rand_int)
Sequence.simple_sequence_map[self] = rand_int
if Worker.stop_ordered:
time_print(f'init() A: stop ordered... stopping now')
return
dict_reorder(Sequence.simple_sequence_map)
if Sequence.prev_seq:
Sequence.sequence_to_sequence_map[self] = Sequence.prev_seq
if Worker.stop_ordered:
time_print(f'init() B: stop ordered... stopping now')
return
dict_reorder(Sequence.sequence_to_sequence_map)
Sequence.prev_seq = self
def __lt__(self, other):
return self.text < other.text
class WorkerSignals(QtCore.QObject):
progress = QtCore.pyqtSignal(int)
stop_me = QtCore.pyqtSignal()
class Worker(QtCore.QRunnable):
def __init__(self, *args, **kwargs):
super().__init__()
self.signals = WorkerSignals()
def stop_me_slot(self):
time_print('stop me slot')
Worker.stop_ordered = True
@QtCore.pyqtSlot()
def run(self):
total_n = 30000
Worker.stop_ordered = False
for n in range(total_n):
progress_pc = int(100 * float(n 1)/total_n)
self.signals.progress.emit(progress_pc)
Sequence()
if Worker.stop_ordered:
time_print(f'run(): stop ordered... stopping now, n {n}')
return
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout()
self.progress = QtWidgets.QProgressBar()
layout.addWidget(self.progress)
start_button = QtWidgets.QPushButton('Start')
start_button.pressed.connect(self.execute)
layout.addWidget(start_button)
self.stop_button = QtWidgets.QPushButton('Stop')
layout.addWidget(self.stop_button)
w = QtWidgets.QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.show()
self.threadpool = QtCore.QThreadPool()
self.resize(800, 600)
def execute(self):
self.worker = Worker()
self.worker.signals.progress.connect(self.update_progress)
self.stop_button.pressed.connect(self.worker.stop_me_slot)
self.threadpool.start(self.worker)
def update_progress(self, progress):
self.progress.setValue(progress)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
app.exec_()
На моей машине примерно до 12% графический интерфейс значительно не реагирует: кнопки не приобретают цвет «наведения» (светло-синий) и, похоже, не могут быть нажаты (хотя нажатие «Стоп» вызывает остановку через много секунд). Периодически появляется страшный спиннер (синий кружок в ОС W10).
Примерно через 12% становится возможным нормально использовать кнопки.
Что я делаю не так?
Ответ №1:
Очень простое решение состоит в том, чтобы «переспать» поток с помощью базового time.sleep
: даже с очень небольшим интервалом это даст достаточно времени основному потоку для обработки своей очереди событий, избегая блокировки пользовательского интерфейса:
def run(self):
total_n = 30000
Worker.stop_ordered = False
for n in range(total_n):
progress_pc = int(100 * float(n 1)/total_n)
self.signals.progress.emit(progress_pc)
Sequence()
if Worker.stop_ordered:
time_print(f'run(): stop ordered... stopping now, n {n}')
return
time.sleep(.0001)
Примечание: этот pyqtSlot
декоратор бесполезен, потому что он работает только для подклассов QObject (которыми QRunnable не является); вы можете удалить его.
Ответ №2:
Python не может запускать более одного потока с интенсивным использованием процессора. Причина этого-ГИЛ. В основном потоки Python не годятся ни для чего, кроме ожидания ввода-вывода.
Если вам нужна часть с интенсивным использованием процессора, попробуйте либо переписать интенсивную часть с помощью Cython, либо использовать multiprocessing
, но тогда время для отправки данных туда и обратно может быть значительным.
Комментарии:
1. Спасибо! Это действительно полезная подсказка. Я просто немного удивлен, что поток графического интерфейса классифицируется как «поток с интенсивным использованием процессора». Возможно, вы имеете в виду, точнее, что если есть ОДИН поток с интенсивным использованием процессора, он на практике не может сосуществовать с ЛЮБЫМИ другими потоками в том же процессе?
2. Я склоняюсь к тому, чтобы переписать в Rust… который уже пару лет кажется новым сексуальным ребенком в квартале. Он специально разработан с учетом многопоточности, и, по-видимому, доступны привязки Qt.
3. @mikerodent: Поток графического интерфейса, к сожалению, может быть чувствителен к процессору. то есть, чтобы в нужное время требовалось достаточно процессора, чтобы не чувствовать себя неспокойно. musicamante выше предложил решение именно для этого: управление потоком с интенсивным использованием процессора, чтобы поток графического интерфейса имел возможность работать чаще.
Ответ №3:
Не может воспроизводиться, пользовательский интерфейс остается отзывчивым даже при ограниченных ресурсах. Вы пробовали запустить его без отладчика?
ГИЛ может быть проблемой, как предполагает @9000.
Или, может быть, eventloop наводнен progress
сигналами, попробуйте излучать их меньше одного для каждой последовательности.
В качестве примечания: программа работает быстрее, если вы не выбрасываете результаты сортировки каждый раз dict_reorder
. попробуйте заменить
dict_reorder(Sequence.simple_sequence_map)
с
Sequence.simple_sequence_map = dict_reorder(Sequence.simple_sequence_map)
и
dict_reorder(Sequence.sequence_to_sequence_map)
с
Sequence.sequence_to_sequence_map = dict_reorder(Sequence.sequence_to_sequence_map)
Комментарии:
1. Спасибо, очень интересно. Я отключил сигналы о прогрессе
if n % 100 == 0:
, и проблема, похоже, решена! На самом деле я не искал способ оптимизировать эту сортировку словаря: на самом деле я потратил немного времени на поиск чего-то (похожего на мой реальный проект), что намеренно очень интенсивно. Вы говорите, что даже при всех сигналах о прогрессе у вас нет проблем. Не могли бы вы сказать, на какой операционной системе вы находитесь? Ваша машина отличается особенно высокими техническими характеристиками? Это i7-7700 3,6 ГГц с большой оперативной памятью и т. Д.2. Если уж на то пошло, что вы подразумеваете под «запуском без отладчика»? Я только что погуглил это, и мне не совсем ясно… Я просто бегу
python my_package
в CLI. Есть ли стандартный отладчик для Python, который вам нужно активно отключить? (простите мое невежество, если это так…)3. @mikerodent Я думал, что вы запускаете его в среде ide с подключенным отладчиком, но вы запускаете его в терминале, поэтому отладчик не задействован. Я запускаю его на win7 на не современном ноутбуке (Core i7 4510U) в режиме настроек энергосбережения наряду с интенсивным процессом видеокодирования процессора (и без него) и не зависал, я запускаю его в win10 (Core i5 4590), и иногда у меня было всего несколько секундных задержек.