Технологии многопоточности в Qt
Qt предлагает множество классов и функций для работы с потоками. Ниже приведены четыре различных подхода, которые программисты Qt могут использовать для реализации многопоточных приложений.
QThread: API низкого уровня с необязательными циклами событий
QThread — основа всего управления потоками в Qt. Каждый экземпляр QThread представляет и управляет одним потоком.
QThread может быть инстанцирован напрямую или унаследован. Инстанцирование QThread обеспечивает параллельный цикл событий, позволяющий вызывать слоты QObject во вторичном потоке. Наследование QThread позволяет приложению инициализировать новый поток перед запуском его цикла событий или выполнять параллельный код без цикла событий.
См. справочную информацию по классу QThread и примеры по работе с потоками для демонстрации использования QThread.
QThreadPool и QRunnable: повторное использование потоков
Частое создание и уничтожение потоков может быть дорогостоящим. Для уменьшения этой накладных расходов существующие потоки могут быть повторно использованы для новых задач. QThreadPool — это коллекция повторно используемых QThreads.
Для выполнения кода в одном из потоков QThreadPool переопределите QRunnable::run() и создайте экземпляр подкласса QRunnable. Используйте QThreadPool::start() для добавления QRunnable в очередь выполнения QThreadPool. Когда поток становится доступным, код внутри QRunnable::run() будет выполняться в этом потоке.
Каждое приложение Qt имеет глобальный пул потоков, доступный через QThreadPool::globalInstance(). Этот глобальный пул потоков автоматически поддерживает оптимальное количество потоков, основываясь на количестве ядер процессора. Однако можно создать и явно управлять отдельным QThreadPool.
Qt Concurrent: Использование API высокого уровня
Модуль Qt Concurrent предоставляет функции высокого уровня, которые обрабатывают некоторые общие шаблоны параллельных вычислений: map, filter и reduce. В отличие от использования QThread и QRunnable, эти функции никогда не требуют использования примитивов низкого уровня для синхронизации потоков, таких как мьютексы или семафоры. Вместо этого они возвращают объект QFuture, который можно использовать для получения результатов функций, когда они будут готовы. QFuture также можно использовать для запроса прогресса вычислений и приостановки/возобновления/отмены вычислений. Для удобства QFutureWatcher позволяет взаимодействовать с QFuture через сигналы и слоты.
Алгоритмы map, filter и reduce модуля Qt Concurrent автоматически распределяют вычисления по всем доступным ядрам процессора, поэтому приложения, написанные сегодня, будут продолжать масштабироваться при развертывании на системах с большим количеством ядер в будущем.
Этот модуль также предоставляет функцию QtConcurrent::run(), которая может запускать любую функцию в другом потоке. Однако QtConcurrent::run() поддерживает только подмножество функций, доступных для map, filter и reduce. QFuture можно использовать для получения возвращаемого значения функции и проверки, запущен ли поток. Однако вызов QtConcurrent::run() использует только один поток, не может быть приостановлен/возобновлен/отменен и не может быть запрошен по прогрессу.
См. документацию модуля Qt Concurrent для получения подробностей о функциях.
WorkerScript: Многопоточность в QML
Тип QML WorkerScript позволяет коду JavaScript работать параллельно с потоком GUI.
Каждый экземпляр WorkerScript может иметь один .js скрипт, прикрепленный к нему. Когда вызывается WorkerScript.sendMessage(), скрипт будет выполняться в отдельном потоке (и в отдельном QML контексте). Когда выполнение скрипта завершается, он может отправить ответ обратно в поток GUI, который вызовет обработчик сигнала WorkerScript.onMessage().
Использование WorkerScript аналогично использованию рабочего QObject, который был перемещен в другой поток. Передача данных между потоками осуществляется через сигналы.
См. документацию WorkerScript для получения подробностей о реализации скрипта и списка типов данных, которые можно передавать между потоками.
Выбор подходящего подхода
Как показано выше, Qt предоставляет различные решения для разработки многопоточных приложений. Правильное решение для данного приложения зависит от цели нового потока и срока его жизни. Ниже приведена сравнительная таблица технологий многопоточности Qt, а также рекомендуемые решения для некоторых примеров использования.
Сравнение решений
| Функция | QThread | QRunnable и QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript |
|---|---|---|---|---|---|
| Язык | C++ | C++ | C++ | C++ | QML |
| Можно указать приоритет потока | Да | Да | |||
| Поток может запускать цикл событий | Да | ||||
| Поток может получать обновления данных через сигналы | Да (получаемый рабочим QObject) | Да (получаемый WorkerScript) | |||
| Поток можно управлять с помощью сигналов | Да (получаемый QThread) | Да (получаемый QFutureWatcher) | |||
| Поток можно отслеживать через QFuture | Частично | Да | |||
| Встроенная возможность приостановки/возобновления/отмены | Да |
Примеры случаев использования
| Срок жизни потока | Операция | Решение |
|---|---|---|
| Один вызов | Запуск новой линейной функции в другом потоке, необязательно с обновлениями прогресса во время выполнения. | Qt предоставляет разные решения:
|
| Один вызов | Запуск существующей функции в другом потоке и получение её возвращаемого значения. | Запустите функцию с помощью QtConcurrent::run(). Используйте QFutureWatcher, чтобы выдать сигнал finished(), когда функция вернёт результат, и вызовите QFutureWatcher::result() для получения возвращаемого значения функции. |
| Один вызов | Выполнение операции со всеми элементами контейнера, используя все доступные ядра. Например, создание миниатюр из списка изображений. | Используйте функцию QtConcurrent::filter() Qt Concurrent для выбора элементов контейнера и функцию QtConcurrent::map() для применения операции к каждому элементу. Для сворачивания выходных данных в один результат используйте QtConcurrent::filteredReduced() и QtConcurrent::mappedReduced() вместо. |
| Один вызов/Постоянный | Выполнение длительного вычисления в чисто QML приложении и обновление GUI, когда результаты готовы. | Разместите код вычисления в .js скрипте и прикрепите его к экземпляру WorkerScript. Вызовите WorkerScript.sendMessage() для запуска вычислений в новом потоке. Пусть скрипт также вызовет sendMessage(), чтобы передать результат обратно в поток GUI. Обработайте результат в onMessage и обновите GUI там. |
| Постоянный | Объект, живущий в другом потоке, который может выполнять разные задачи по запросу и/или получать новые данные для работы. | Унаследуйте от QObject для создания рабочего объекта. Создайте этот рабочий объект и QThread. Переместите рабочий объект в новый поток. Отправьте команды или данные в объект рабочего объекта через связанные с очередью сигналы-слоты. |
| Постоянный | Многократное выполнение дорогостоящей операции в другом потоке, где потоку не нужно получать какие-либо сигналы или события. | Напишите бесконечный цикл непосредственно в переопределении QThread::run(). Запустите поток без цикла событий. Пусть поток посылает сигналы для отправки данных обратно в поток GUI. |
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.15/threads-technologies.html