Почему задержка меняет количество запущенных QThreads?

В следующем примере программы используется QThread экземпляры для запуска заданий из очереди.

from queue import Queue
from sys import argv
from threading import Lock
from time import sleep
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication

class Worker(QObject):
    finished = pyqtSignal()

    def __init__(self, number):
        super().__init__()
        self.number = number

    @pyqtSlot()
    def work(self):
        while True:
            job = queue.get()
            if job is None:
                self.finished.emit()
                return
            with lock:
                print('worker={} job={}'.format(self.number, job))
            sleep(1)

app = QApplication(argv)
lock = Lock()
queue = Queue()
threads = []
nthreads = 4
for ithread in range(nthreads):
    thread = QThread()
    worker = Worker(ithread + 1)
    worker.moveToThread(thread)
    thread.started.connect(worker.work)
    worker.finished.connect(thread.quit)
    thread.start()
    threads += [thread]
    #-----------
    sleep(1e-10)
    #-----------

for ijob in range(10):
    queue.put(ijob + 1)

for _ in range(nthreads):
    queue.put(None)
for thread in threads:
    thread.wait()

С sleep вызов (см. отмеченную строку), все потоки работают как положено. Выход:

worker=1 job=1
worker=2 job=2
worker=3 job=3
worker=4 job=4
[…]

Без вызова выполняется произвольное количество потоков. Выход:

worker=4 job=1
worker=4 job=2
worker=4 job=3
worker=4 job=4
[…]

Я проверил это с PyQt 5 в Python 3.6.2, другими версиями, переменной продолжительностью сна, различными порядками операторов и циклом запуска событий.

Почему sleep позвонить изменить количество запущенных потоков?

1 ответ

Решение

Первые три рабочих объекта склонны к сборке мусора, а четвертый остается глобальной переменной. sleep позволяет каждому потоку достаточно времени для вызова work метод связанного с ним работника, и это сохранит их всех живыми (так как методы запускают циклы while). Без sleepпервые три работника будут немедленно собраны, оставив только четвертого для обработки очереди. Если рабочие хранятся в списке (так же, как потоки), вы должны увидеть, что все они используются (с или без сна).

Чтобы доказать, что это происходит, вы можете добавить это к Worker учебный класс:

class Worker(QObject)
    ...

    def __del__(self):
        print('deleted:', self.number)
Другие вопросы по тегам