Технологии многопоточности в 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 выполняться параллельно с потоком графического интерфейса.
Каждый экземпляр WorkerScript может иметь один .js скрипт, прикрепленный к нему. При вызове WorkerScript.sendMessage() скрипт будет выполняться в отдельном потоке (и в отдельном контексте QML). После завершения выполнения скрипта он может отправить ответ обратно в поток графического интерфейса, который вызовет обработчик сигнала 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, и обновление графического интерфейса, когда результаты будут готовы. | Разместите код вычисления в .js скрипте и прикрепите его к экземпляру WorkerScript. Вызовите WorkerScript.sendMessage() для запуска вычисления в новом потоке. Пусть скрипт также вызовет sendMessage(), чтобы вернуть результат в поток графического интерфейса. Обработайте результат в onMessage и обновите графический интерфейс там. |
| Постоянный | Иметь объект, живущий в другом потоке, который может выполнять различные задачи по запросу и/или может получать новые данные для работы. | Создайте подкласс QObject для создания рабочего объекта. Создайте экземпляр этого рабочего объекта и QThread. Переместите рабочий объект в новый поток. Отправляйте команды или данные рабочему объекту через очереди соединений сигнал-слот. |
| Постоянный | Повторяйте выполнение дорогостоящей операции в другом потоке, где потоку не нужно получать какие-либо сигналы или события. | Напишите бесконечный цикл непосредственно внутри переопределения QThread::run(). Запустите поток без цикла событий. Позвольте потоку отправлять сигналы для отправки данных обратно в поток графического интерфейса. |
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.14/threads-technologies.html