Один из самых интригующих вариантов реализации многопоточной обработки
с помощью QThread описывается в разделе
Multithreading Technologies in Qt
документации Qt5 следующим образом:
- время жизни потока — постоянно, от старта приложения до его завершения,
- функционирование — с помощью объекта, живущего в дополнительном потоке,
который может выполнять различные задачи по запросу и/или может получать
новые данные для работы,
- реализация — создать подкласс QObject для создания исполнителя работ,
создать экземпляр исполнителя на базе этого подкласса и экземпляр
дополнительного потока QThread, переместить экземпляр исполнителя
во вновь созданный поток, отправлять команды или данные объекту
исполнителя через соединения сигнал-слот с постановкой в очередь
(queued connection — см. Qt Namespace).
Самое замечательное здесь то, что и команды, и данные отправляются непосредственно
исполнителю.
Вот для того, чтобы разобраться как всё это работает, что можно делать,
что нельзя, какие есть ограничения и неожиданности, как со всем этим
управляться и где это может найти применение, и была написана программа
Зонд дополнительного потока (The Thread Probe).
Записи протокола содержат некое описание произошедшего, включая:
- имя объекта с именем метода/слота, сгенерировавшего запись,
- 16-ричный идентификатор этого объекта (в круглых скобках),
- 16-ричный идентификатор потока, в котором живёт этот объект (в угловых скобках),
- также может присутствовать 16-ричный идентификатор потока,
управляющего дополнительным потоком (в обрамлении эвёздочек).
Объект дополнительного потока и объекты, живущие в дополнительном потоке,
формируя нагрузку, отправляемую сигналами, помечают начало каждой строки
символами --->, что позволяет отличить их в протоколе от остальных записей.
Этой же цели служит и цветовая дифференциация в окне просмотра протокола.
Например,
<0x7f1b9c26c948> mainWin.on_check_started() reported 25/01/2021 10:27:58
testThread (0x7f1bae0ceb88) started
sender is testThread (0x7f1bae0ceb88)
<0x7f1b9c26c948> mainWin.to_run_short() reported 25/01/2021 10:28:07
shortSignal emitted
<0x7f1b9c26c948> mainWin.on_check_test() reported 25/01/2021 10:28:07
---> <0x7f1bae0ceb88> testFinite.shortTest() reported 25/01/2021 10:28:07
---> testFinite.shortTest() started
sender is testFinite (0x7f1bae0ceca8)
<0x7f1b9c26c948> mainWin.to_run_probe() reported 25/01/2021 10:28:09
probeSignal emitted
<0x7f1b9c26c948> mainWin.on_check_probe() reported 25/01/2021 10:28:09
---> <0x7f1b9c26c948> testThread.probeTest() reported 25/01/2021 10:28:09
---> testThread (0x7f1bae0ceb88) is managed from *0x7f1b9c26c948*
---> isRunning()->True isFinished()->False isInterruptionRequested()->False
sender is testThread (0x7f1bae0ceb88)
Разбирая этот протокол построчно, можно выяснить:
<0x7f1b9c26c948> mainWin.on_check_started() reported 25/01/2021 10:27:58
что объект главного окна
mainWin живёт в GUI-потоке с идентификатором
0x7f1b9c26c948,
testThread (0x7f1bae0ceb88) started
что объект дополнительного потока
testThread имеет идентификатор
0x7f1bae0ceb88,
---> <0x7f1bae0ceb88> testFinite.shortTest() reported 25/01/2021 10:28:07
что объект-исполнитель работ
testFinite живёт в потоке с идентификатором
0x7f1bae0ceb88, т.е. в дополнительном потоке,
---> testThread (0x7f1bae0ceb88) is managed from *0x7f1b9c26c948*
что объект дополнительного потока
testThread управляется из потока
с идентификатором
0x7f1b9c26c948, т.е. из GUI-потока.
ПРИМЕРНЫЙ СЦЕНАРИЙ РАБОТЫ С ПРИЛОЖЕНИЕМ
Ниже представлена некая последовательность действий, выполняя которую
можно проверить и уточнить то, что говорится в документации:
- объекты-исполнители выполняются в дополнительном потоке:
- запустить приложение,
- запустить дополнительный поток.
Обратить внимание на идентификатор объекта дополнительного потока
testThread.
Именно этот идентификатор должны указывать объекты-исполнители
в качестве идентификатора потока, в котором они живут,
- запустить короткий тест.
Убедиться, что идентификатор потока, в котором живёт объект-исполнитель
testFinite совпадает с идентификатором объекта testThread,
- выполняющийся тест не мешает запросу состояния потока:
- запустить прерываемый бесконечный тест,
- запросить сведения о дополнительном потоке.
Убедиться, что сведения предоставляются, несмотря на выполняющийся тест,
поскольку сам объект дополнительного потока testThread живёт в GUI-потоке,
- завершение потока возможно только после завершения теста:
- завершить дополнительный поток.
Убедиться, что тест продолжает исполняться,
- запросить прерывание в дополнительном потоке.
Убедиться, что поток завершится после завершения теста,
- возможен рестарт потока:
- запросить сведения о дополнительном потоке.
Убедиться, что поток завершён (isRunning->False
и isFinished->True),
- запустить дополнительный поток,
- запросить сведения о дополнительном потоке.
Убедиться, что поток стартовал (isRunning->True
и isFinished->False),
а запрос прерывания сброшен (isInterruptionRequested->False),
- очередь обслуживает поток целиком, а не отдельные объекты:
- запустить прерываемый бесконечный тест,
- запустить короткий тест.
Убедиться, что короткий тест не запустился.
- запросить прерывание в дополнительном потоке.
Убедиться, что короткий тест стартует сразу после завершения предыдущего теста.
- задания, оставшиеся в очереди после завершения потока,
начнут выполняться сразу после рестарта потока:
- запустить длинный тест,
- не дожидаясь завершения длинного теста,
запустить короткий тест и бесконечный тест,
затем завершить дополнительный поток,
- дождаться завершения длинного теста и завершения потока.
Убедиться, что ранее запущенные короткий и бесконечный тесты
так и не стартовали,
- запустить дополнительный поток.
Убедиться, что ранее запущенный короткий тест стартовал,
- дождаться начала выполнения бесконечного теста и
завершить работу приложения.
Убедиться, что выполняющийся тест не мешает закрытию приложения,
- графический интерфейс многопоточен:
- запустить приложение из командной строки, указав параметр
(не важно какой, главное, чтобы он был),
- просматривая вывод контрольных точек в сессии командной строки,
обратить внимание на идентификатор потока, в котором живёт объект
главного окна (mainWin, он же [ProbeMainWindow]
во время инициализации),
- поскольку объект главного окна точно живёт в GUI-потоке,
то можно убедиться, что GUI перемещается из одного потока в другой
в процессе инициализации приложения,
- запросить сведения о дополнительном потоке,
- вызвать справку, закрыть её и снова запросить сведения о дополнительном потоке,
- просматривая полученный протокол работы, обратить внимание
на идентификатор потока, в котором живёт объект главного окна mainWin
до вызова справки и после него,
- повторив несколько раз запрос сведений о дополнительном потоке
после вызова справки, можно попытаться количественно оценить
набор потоков между которыми переключается GUI.
- и так далее...
Можно, например, попробовать перезапустить поток после терминирования :).
ОБ АВТОРЕ
Автора сего поделия можно найти на Cyberforum.ru под ником iamvic.