Spec-Zone.ru › Qt 5.14

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

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

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

Spec-Zone.ru

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