Spec-Zone.ru › Qt 6.0

Синхронизация потоков

Хотя потоки предназначены для выполнения кода параллельно, иногда потокам необходимо останавливаться и ожидать других потоков. Например, если два потока пытаются одновременно записать в одну и ту же переменную, результат будет неопределённым. Принцип принуждения потоков ожидать друг друга называется взаимным исключением. Это распространённый метод защиты общих ресурсов, таких как данные.

Qt предоставляет как низкоуровневые примитивы, так и высокоуровневые механизмы для синхронизации потоков.

Низкоуровневые примитивы синхронизации

QMutex — это базовый класс для обеспечения взаимного исключения. Поток блокирует мьютекс для доступа к общему ресурсу. Если второй поток пытается заблокировать мьютекс, когда он уже заблокирован, второй поток будет приостановлен, пока первый поток не завершит свою задачу и не разблокирует мьютекс.

QReadWriteLock похож на QMutex, за исключением того, что он различает доступ для чтения и записи. Когда к данным не происходит запись, для нескольких потоков безопасно одновременно читать из них. QMutex заставляет несколько читателей по очереди читать общие данные, но QReadWriteLock позволяет одновременное чтение, что улучшает параллелизм.

QSemaphore — это обобщение QMutex, которое защищает определённое количество идентичных ресурсов. В отличие от QMutex, который защищает ровно один ресурс. Пример использования семафоров: синхронизация доступа к кольцевому буферу между производителем и потребителем.

QWaitCondition синхронизирует потоки не путём обеспечения взаимного исключения, а путём предоставления условной переменной. В то время как другие примитивы заставляют потоки ожидать, пока ресурс не будет разблокирован, QWaitCondition заставляет потоки ждать, пока не будет выполнено определённое условие. Чтобы позволить ожидающим потокам продолжить работу, вызовите wakeOne() для пробуждения случайно выбранного потока или wakeAll() для одновременного пробуждения всех.

Примечание: классы синхронизации Qt полагаются на использование правильно выровненных указателей. Например, вы не можете использовать упакованные классы с MSVC.

Эти классы синхронизации могут использоваться для обеспечения потокобезопасности метода. Однако это приводит к снижению производительности, поэтому большинство методов Qt не делают потокобезопасными.

Риски

Если поток блокирует ресурс, но не разблокирует его, приложение может заблокироваться, потому что ресурс станет постоянно недоступным для других потоков. Это может произойти, например, если возникает исключение, и текущая функция возвращается без освобождения блокировки.

Ещё один похожий сценарий — тупик. Предположим, что поток A ожидает, пока поток B разблокирует ресурс. Если поток B также ожидает, пока поток A разблокирует другой ресурс, оба потока будут ждать вечно, и приложение заблокируется.

Удобные классы

QMutexLocker, QReadLocker и QWriteLocker — это удобные классы, которые облегчают использование QMutex и QReadWriteLock. Они блокируют ресурс при создании и автоматически разблокируют его при уничтожении. Они предназначены для упрощения кода, использующего QMutex и QReadWriteLock, тем самым уменьшая вероятность случайной постоянной блокировки ресурса.

Высокоуровневые очереди событий

Система событий Qt очень полезна для межпоточной связи. Каждый поток может иметь свой собственный цикл обработки событий. Для вызова слота (или любого вызываемого метода) в другом потоке поместите этот вызов в цикл обработки событий целевого потока. Это позволяет целевому потоку завершить текущую задачу перед запуском слота, в то время как исходный поток продолжает работать параллельно.

Для размещения вызова в цикле обработки событий выполните подключение сигнала-слота в очереди. При каждом отправлении сигнала его аргументы будут записаны системой событий. Поток, в котором находится получатель сигнала, затем выполнит слот. В качестве альтернативы, вызовите QMetaObject::invokeMethod(), чтобы добиться того же эффекта без сигналов. В обоих случаях необходимо использовать подключение в очереди, потому что прямое подключение опускает систему событий и выполняет метод немедленно в текущем потоке.

В отличие от использования низкоуровневых примитивов, при использовании системы событий для синхронизации потоков нет риска возникновения тупиков. Однако система событий не обеспечивает взаимное исключение. Если вызываемые методы обращаются к общим данным, они по-прежнему должны защищаться низкоуровневыми примитивами.

Тем не менее, система событий Qt вместе с неявными общими структурами данных предлагает альтернативу традиционной блокировке потоков. Если используются только сигналы и слоты, и переменные не общие между потоками, многопоточное приложение может обойтись без низкоуровневых примитивов.

См. также QThread::exec() и Потоки и QObjects.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.0/threads-synchronizing.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API