Spec-Zone.ru › Qt 6.1

Потоки и QObjects

QThread наследует QObject. Он испускает сигналы, чтобы указать, что поток начал или завершил выполнение, а также предоставляет несколько слотов.

Более интересно, что объекты QObject могут использоваться в нескольких потоках, испускать сигналы, которые вызывают слоты в других потоках, и публиковать события объектам, «живущим» в других потоках. Это возможно, потому что каждый поток может иметь свою собственную очередь событий.

Взаимовложенность QObject

QObject является реентерабельным. Большинство его подклассов, не относящихся к графическому интерфейсу, такие как QTimer, QTcpSocket, QUdpSocket и QProcess, также являются реентерабельными, что позволяет использовать эти классы одновременно из нескольких потоков. Обратите внимание, что эти классы предназначены для создания и использования внутри одного потока; создание объекта в одном потоке и вызов его функций из другого потока не гарантируется. Есть три ограничения, о которых следует помнить:

  • Потомок QObject всегда должен создаваться в том же потоке, где был создан родитель. Это подразумевает, среди прочего, что вы никогда не должны передавать объект QThread (this) в качестве родителя объекта, созданного в этом потоке (поскольку сам объект QThread был создан в другом потоке).
  • Объекты, управляемые событиями, могут использоваться только в одном потоке. В частности, это относится к механизму таймеров и модулю сети механизм таймеров и модулю сети модуль сети. Например, вы не можете запустить таймер или подключить сокет в потоке, который не является потоком объекта поток объекта.
  • Вы должны убедиться, что все объекты, созданные в потоке, удалены перед удалением QThread. Это можно легко сделать, создав объекты на стеке в реализации вашего метода run().

Хотя QObject является реентерабельным, классы графического интерфейса, в частности QWidget и все его подклассы, не являются реентерабельными. Они могут использоваться только из главного потока. Как отмечалось ранее, QCoreApplication::exec() также должен вызываться из этого потока.

На практике невозможность использования классов графического интерфейса в потоках, отличных от главного, легко обходится путем размещения трудоемких операций в отдельном рабочем потоке и отображения результатов на экране в главном потоке, когда рабочий поток завершен. Такой подход используется для реализации примера Мандельброта и примера блокирующего клиента Fortune.

В общем случае создание QObjects до QApplication не поддерживается и может приводить к странным сбоям при выходе, в зависимости от платформы. Это означает, что статические экземпляры QObject также не поддерживаются. Правильно структурированное одно- или многопоточное приложение должно сделать QApplication первым созданным и последним удаленным QObject.

Очередь событий на поток

Каждый поток может иметь свою собственную очередь событий. Начальный поток запускает свою очередь событий с помощью QCoreApplication::exec(), или для приложений с графическим интерфейсом, состоящим из одного диалогового окна, иногда с помощью QDialog::exec(). Другие потоки могут запустить очередь событий с помощью QThread::exec(). Как и QCoreApplication, QThread предоставляет функцию exit(int) и слот quit().

Очередь событий в потоке позволяет потоку использовать определенные классы Qt, не относящиеся к графическому интерфейсу, которые требуют наличия очереди событий (такие как QTimer, QTcpSocket и QProcess). Это также позволяет подключать сигналы из любых потоков к слотам определенного потока. Это более подробно объясняется в разделе Сигналы и слоты между потоками ниже.

Threads, objects, and event loops

Экземпляр QObject считается «живущим» в том потоке, в котором он был создан. События для этого объекта обрабатываются очередью событий этого потока. Поток, в котором «живет» QObject, доступен с помощью QObject::thread().

Функция QObject::moveToThread() изменяет привязку потока для объекта и его потомков (объект нельзя переместить, если у него есть родитель).

Вызов delete объекта QObject из потока, отличного от потока, в котором объект «владеет» объектом (или доступ к объекту другими способами), небезопасен, если вы не гарантируете, что объект не обрабатывает события в этот момент. Вместо этого используйте QObject::deleteLater(), и будет опубликовано событие DeferredDelete, которое в конечном итоге получит очередь событий потока объекта. По умолчанию поток, в котором «владеет» QObject, — это поток, в котором QObject был создан, но не после вызова QObject::moveToThread().

Если очередь событий не работает, события не будут доставлены объекту. Например, если вы создаёте объект QTimer в потоке, но никогда не вызываете exec(), QTimer никогда не выпустит свой сигнал timeout(). Вызов deleteLater() также не сработает. (Эти ограничения применимы и к главному потоку.)

Вы можете вручную публиковать события любому объекту в любом потоке в любое время, используя безопасную для потоков функцию QCoreApplication::postEvent(). События автоматически будут обрабатываться очередью событий потока, в котором был создан объект.

Фильтры событий поддерживаются во всех потоках с ограничением, что контролирующий объект должен «жить» в том же потоке, что и контролируемый объект. Аналогично, QCoreApplication::sendEvent() (в отличие от postEvent()) может быть использовано только для отправки событий объектам, «живущим» в том потоке, из которого вызывается функция.

Доступ к подклассам QObject из других потоков

QObject и все его подклассы не являются потокобезопасными. Это включает всю систему доставки событий. Важно помнить, что очередь событий может доставлять события вашему подклассу QObject, в то время как вы обращаетесь к объекту из другого потока.

Если вы вызываете функцию подкласса QObject, который не «живет» в текущем потоке, и объект может получать события, вы должны защитить весь доступ к внутренним данным подкласса QObject с помощью мьютекса; в противном случае вы можете столкнуться с ошибками или другим нежелательным поведением.

Как и другие объекты, объекты QThread «живут» в том потоке, в котором был создан объект, — не в потоке, который создается при вызове QThread::run(). Обычно небезопасно предоставлять слоты в вашем подклассе QThread, если вы не защищаете переменные-члены с помощью мьютекса.

С другой стороны, вы можете безопасно испускать сигналы в вашей реализации QThread::run(), так как отправка сигналов безопасна для потоков.

Сигналы и слоты между потоками

Qt поддерживает следующие типы соединений сигналов и слотов:

  • Автоматическое соединение (по умолчанию) Если сигнал отправляется в потоке, к которому принадлежит принимающий объект, поведение такое же, как при прямом соединении. В противном случае поведение такое же, как при соединении в очереди.
  • Прямое соединение Слот вызывается немедленно при отправке сигнала. Слот выполняется в потоке отправителя, который необязательно является потоком получателя.
  • Соединение в очереди Слот вызывается при возвращении управления к очереди событий потока получателя. Слот выполняется в потоке получателя.
  • Блокирующее соединение в очереди Слот вызывается так же, как при соединении в очереди, за исключением того, что текущий поток блокируется до возврата слота.

    Примечание: Использование этого типа для соединения объектов в одном потоке приведет к тупиковой ситуации.

  • Уникальное соединение Поведение такое же, как при автоматическом соединении, но соединение создается только в том случае, если оно не дублирует существующее соединение. Т.е., если один и тот же сигнал уже подключен к тому же слоту для той же пары объектов, то соединение не создаётся и connect() возвращает false.

Тип соединения можно указать, передав дополнительный аргумент в connect(). Имейте в виду, что использование прямых соединений, когда отправитель и получатель находятся в разных потоках, небезопасно, если в потоке получателя работает очередь событий, по той же причине, что вызов любой функции объекта, живущего в другом потоке, небезопасен.

QObject::connect() сам по себе безопасен для потоков.

В примере Мандельброта используется соединение в очереди для связи между рабочим потоком и главным потоком. Чтобы избежать зависания очереди событий главного потока (и, как следствие, пользовательского интерфейса приложения), все вычисления фрактала Мандельброта выполняются в отдельном рабочем потоке. Поток посылает сигнал, когда он закончил рендеринг фрактала.

Аналогично, пример блокирующего клиента Fortune использует отдельный поток для асинхронного взаимодействия с сервером TCP.

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/threads-qobject.html

Spec-Zone.ru

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