Spec-Zone.ru › Qt 5.11

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

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

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

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

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

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

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

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

Примечание: классы синхронизации 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/archives/qt-5.11/threads-synchronizing.html

Spec-Zone.ru

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