Spec-Zone.ru › Qt 5.13

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

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

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

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

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

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

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

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

Пример условных переменных Wait Conditions Example показывает, как решить проблему производителя-потребителя с использованием 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-5.13/threads-synchronizing.html

Spec-Zone.ru

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