Технологии многопоточности в 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() для получения возвращаемого значения функции. |
| Один вызов | Выполнить операцию над всеми элементами контейнера, используя все доступные ядра. Например, создание миниатюр из списка изображений. | Используйте функцию Qt Concurrent's QtConcurrent::filter() для выбора элементов контейнера и функцию 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.12/threads-technologies.html