Spec-Zone.ru › Qt 5.15

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

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

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

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

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

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

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

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

Пример с условными переменными демонстрирует, как решить проблему «производитель-потребитель» с помощью QWaitCondition вместо QSemaphore.

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

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

Риски

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

Еще один подобный сценарий — это тупик. Например, предположим, что поток А ожидает, пока поток В разблокирует ресурс. Если поток В также ожидает, пока поток А разблокирует другой ресурс, оба потока будут ждать вечно, поэтому приложение заблокируется.

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

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-5.15/threads-synchronizing.html

Spec-Zone.ru

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