Spec-Zone.ru › Qt

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

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

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

Spec-Zone.ru

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