Python, PyQt5 GUI начинает тормозить при запуске большого количества QThreads
Я пишу приложение на Python 3.9 с пользовательским интерфейсом, используя PyQt5. В моем приложении я использую большое количество потоков QThread ( основная функция каждого потока - отправка HTTP запросов, обработка ответов, и передача результатов выполнения (посредством механизма сигнал-слот'ов) обратно в поток GUI, для отображения данных внутри QTableWIdget) Используется обычно от 300 и до 5000 потоков единовременно
Суть проблемы следующая - когда запускается такое количество потоков, и начинается их выполнение, интерфейс начинает сильно тормозить, до момента пока не закончится выполнение всех потоков. Отключение сигналов, которые связаны со слотами , изменяющими виджеты в GUI - ничего не меняет, GUI все также продолжает тормозить.
Для теста, я пробовал запускать такое же количество потоков, которые во время своего выполнения, только испускают аналогичные сигналы в цикле с небольшой задержкой, без выполнения другой логики - в таком случае все становится нормально, GUI без задержек обновляется и отвечает на все пользовательские команды. Из чего я могу сделать вывод, что проблема не в большом количестве испускаемых\принимаемых сигналов.
Вопрос , что провоцирует торможение GUI, в моем случае, и есть ли какие-то возможные решения чтобы сделать мой интерфейс более отзывчивым в данной ситуации? Читал про GIL, но не уверен, что это связано с ним.
Пример кода в котором присутствует проблема, почищенный от лишних функций:
main.py
import sys
import os
import time
import requests
from datetime import datetime
from PyQt5 import QtWidgets, QtCore, QtGui, uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
MainWidget, _ = uic.loadUiType(os.path.join(os.path.dirname(__file__),'ui.ui'))
class MainWindow(QtWidgets.QMainWindow, MainWidget):
def __init__(self):
super(MainWindow,self).__init__()
self.setupUi(self)
self.threads = {}
self.loaddata()
self.startButton.clicked.connect(self.start_work)
def loaddata(self):
""" Заполнение таблицы (виджет уже создан ранее в Qt Designer) """
self.table.setRowCount(500)
self.table.setColumnCount(5)
for i in range(500):
self.table.setItem(i, 0, QtWidgets.QTableWidgetItem(str(i)))
self.table.setItem(i, 1, QtWidgets.QTableWidgetItem(str(i)))
self.table.setItem(i, 2, QtWidgets.QTableWidgetItem(str(i)))
self.table.setItem(i, 3, QtWidgets.QTableWidgetItem(str(i)))
self.table.setItem(i, 4, QtWidgets.QTableWidgetItem(str(i)))
def start_work(self):
""" Запускаем потоки
Кол-во потоков равно кол-ву строк в таблице
В данном примере запустится 500 потоков """
for row in range(self.table.rowCount()):
item = self.table.item(row,0)
worker = Worker(item)
thread = QThread()
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.mysignal.connect(self.on_signal)
worker.finished.connect(self.on_finish_thread)
self.threads[str(item)] = [thread, worker]
thread.start()
worker.deleteLater()
def on_finish_thread(self, item):
""" Останавливаем поток и удаляем QThread и QObject из словаря """
thread = self.threads[str(item)][0]
worker = self.threads[str(item)][1]
thread.quit()
thread.wait()
try:
del self.threads[str(item)]
except Exception as e:
print(e)
def on_signal(self, item, msg):
""" Устанавливается значение в соответствующую ячейку таблицы """
self.table.item(item.row(), 2).setText(msg)
class Worker(QObject):
mysignal = pyqtSignal(object, str)
finished = pyqtSignal(object)
def __init__(self, item):
self.item = item
super().__init__()
def run(self):
""" Отправляем запросы в цикле и испускаем сигналы с результатом """
for i in range(100):
try:
requests.get('https://vk.com/')
self.mysignal.emit(self.item, f'Response:ok {datetime.now()}')
except Exception as e:
self.mysignal.emit(self.item, f'Response:error {e}')
time.sleep(0.1)
self.finished.emit(self.item)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
ui.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>570</width>
<height>684</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QTableWidget" name="table">
<property name="geometry">
<rect>
<x>48</x>
<y>126</y>
<width>457</width>
<height>359</height>
</rect>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>1</string>
</property>
</column>
<column>
<property name="text">
<string>2</string>
</property>
</column>
<column>
<property name="text">
<string>3</string>
</property>
</column>
</widget>
<widget class="QPushButton" name="startButton">
<property name="geometry">
<rect>
<x>227</x>
<y>523</y>
<width>75</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>start</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
Ответы (1 шт):
В итоге переписал код с использованием библиотеки aiohttp и обработкой всех http-запросов в одном async-потоке, все работает идеально, GUI совершенно не тормозит, даже при 5-10 тысячях запущенных потоков. Видимо библиотека requests совершенно не подходит для подобных задач)