Технологии многопоточности в Qt
Qt предлагает множество классов и функций для работы с потоками. Ниже приведены четыре различных подхода, которые программисты Qt могут использовать для реализации многопоточных приложений.
QThread: Базовый API с необязательными циклами событий
QThread является основой всего управления потоками в Qt. Каждый экземпляр QThread представляет и контролирует один поток.
QThread может быть создан непосредственно или путем наследования. Создание QThread обеспечивает параллельный цикл событий, позволяющий вызывать слоты QObject во вторичном потоке. Наследование QThread позволяет приложению инициализировать новый поток перед запуском его цикла событий или запускать параллельный код без цикла событий.
См. справочную информацию по классу QThread и примеры работы с потоками для демонстраций использования QThread.
QThreadPool и QRunnable: Переиспользование потоков
Частое создание и уничтожение потоков может быть дорогостоящим. Для уменьшения этих накладных расходов существующие потоки могут быть повторно использованы для новых задач. QThreadPool представляет собой коллекцию переиспользуемых QThread.
Для выполнения кода в одном из потоков 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 аналогично использованию объекта worker QObject, который был перемещён в другой поток. Передача данных между потоками осуществляется через сигналы.
См. документацию WorkerScript для получения подробной информации о реализации скрипта и списка типов данных, которые могут передаваться между потоками.
Выбор подходящего подхода
Как показано выше, Qt предоставляет различные решения для разработки многопоточных приложений. Правильное решение для данного приложения зависит от назначения нового потока и срока его жизни. Ниже приведена таблица сравнения технологий многопоточности Qt, за которой следуют рекомендуемые решения для некоторых примеров вариантов использования.
Сравнение решений
| Функция | QThread | QRunnable и QThreadPool | QtConcurrent::run() | Qt Concurrent (Map, Filter, Reduce) | WorkerScript |
|---|---|---|---|---|---|
| Язык | C++ | C++ | C++ | C++ | QML |
| Можно указать приоритет потока | Да | Да | |||
| Поток может запускать цикл событий | Да | ||||
| Поток может получать обновления данных через сигналы | Да (получаемые объектом worker 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, чтобы создать рабочий объект. Создайте экземпляр этого объекта worker и QThread. Переместите worker в новый поток. Отправьте команды или данные в объект worker через очереди соединений сигнал-слот. |
| Постоянный | Повторно выполнять дорогостоящую операцию в другом потоке, где потоку не нужно получать сигналы или события. | Напишите бесконечный цикл непосредственно в переопределении QThread::run(). Запустите поток без цикла событий. Пусть поток отправляет сигналы, чтобы отправлять данные обратно в поток GUI. |
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/threads-technologies.html