Многопоточные технологии в 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/archives/qt-5.11/threads-technologies.html