Написал программу, которая прекрасно работала в одном потоке. Но после добавления рабочих потоков, время от времени стали выскакивать чудесные баги. Много часов провел за отладкой, расставил кучу принтов и…. получил такую штуку - время от времени сигналы высылаемые из рабочих потоков уходят вникуда. То есть сигнал высылается, а подписанный на него слот не вызывается.
Для проверки написал тестовую мини-программку, которая демонстрирует проблему.
Создаю очередь Queue, куда буду класть задания.
Создаю 4 потока Worker, которые в бесконечном цикле проверяют очередь.
Задание - объект простенького класса Task. Класс - наследуется от QObject, чтобы мог посылать сигнал ‘taskEnd’. Конструктор Task получает функцию, которую вызовет в потоке и параметр для этой функции.
func - функция, которая будет вызвана в потоке. Она параметром получает число, прибавляет к нему 1 и возвращает результат.
TASK_NUMBER - количество заданий, то есть количество объектов Task, которые будут обрабатыватся потоками.
Как пример работает:
щелкаем по кнопке ‘start’ - программа создает TASK_NUMBER объектов заданий, подписывает на сигнал ‘taskEnd’ обработчик - метод формы success, и кладет объект задания в Queue.
свободный поток забирает задание и запускает его. Метод run объекта задания вызывает “рабочую” функцию func, а результат ее выполнения посылает с сигналом ‘taskEnd’.
Метод success, который подписан на сигнал, считает сколько раз был вызван….
Проблема в том, что на 100 000 заданий примерно в четырех случаях из пяти приходится 99 996 - 99 999 вызовов метода success.
В PyQt бага или я чего-то не понимаю?
Python 2.6
Qt 4.6
PyQt 4.7
Windows Vista
Код:
# coding: utf-8
import sys
import Queue
import time
from threading import Thread
from PyQt4.QtGui import *
from PyQt4.QtCore import *
TASK_NUMBER = 100000
def func(i):
return i + 1
class Worker(Thread):
def run(self):
while True:
try:
t = queue.get()
except Queue.Empty:
continue
else:
time.sleep(0.02)
t.run()
class Task(QObject):
def __init__(self, target=None, args=()):
QObject.__init__(self)
self.target = target
self.args = args
def run(self):
r = self.target(*self.args)
self.emit(SIGNAL('taskEnd(PyQt_PyObject)'), r)
class Form(QWidget):
def __init__(self, queue):
QWidget.__init__(self)
self.queue = queue
self.get = 0
self.tasks = []
self.button = QPushButton('start')
QObject.connect(self.button, SIGNAL('clicked()'), self.start)
self.put_label = QLabel('Put: 0')
self.get_label = QLabel('Get: 0')
v = QVBoxLayout()
v.addWidget(self.put_label)
v.addWidget(self.get_label)
v.addWidget(self.button)
self.setLayout(v)
def start(self):
for i in xrange(TASK_NUMBER):
t = Task(func, args=[i])
self.tasks.append(t)
QObject.connect(t, SIGNAL('taskEnd(PyQt_PyObject)'), self.success)
self.queue.put(t)
self.put_label.setText('Put: ' + str(TASK_NUMBER))
def success(self, r):
self.get += 1
self.get_label.setText('Get: ' + str(self.get))
queue = Queue.Queue()
t1 = Worker()
t1.start()
t2 = Worker()
t2.start()
t3 = Worker()
t3.start()
t4 = Worker()
t4.start()
app = QApplication(sys.argv)
f = Form(queue)
f.show()
sys.exit(app.exec_())