|
[Previous: Reentrancy and Thread-Safety]
[Thread Support in Qt]
[Next: Concurrent Programming]
Потоки и объекты QObject
QThread унаследован от QObject. Он испускает сигналы, сообщающие о том, что поток начал или закончил работу, а также предоставляет несколько слотов.
Более интересным является то, что объекты QObject могут использоваться в многопоточном приложении, испускать сигналы, приходящие в слоты, находящиеся в других потоках, и посылать события объектам, "живущим" в других потоках. Это возможно, потому что каждый поток имеет собственный цикл обработки событий.
Темы:
Реентерабельность QObject
QObject реентерабелен. Most of its non-GUI subclasses, such as QTimer, QTcpSocket, QUdpSocket, QFtp, and QProcess, are also reentrant, making it possible to use these classes from multiple threads simultaneously. Заметим, что эти классы спроектированы для создания и использования в одном потоке; создание объекта в одном потоке и вызов его функций из другого может и не сработать. Вы должны знать о трёх ограничениях:
- Дочерний объект QObject должен всегда создаваться в том же потоке, что и родительский объект. Кроме всего прочего, это подразумевает, что вы не должны передавать объект QThread (this) как родителя объекта, созданного в своём потоке (так как объект QThread сам создан в другом потоке).
- Объекты, управляемые событиями, могут быть использованы только в одном потоке. В частности, это относится к механизму таймера и модулю работы с сетью. Например, вы не можете запустить таймер или присоединить сокет к потоку, который не является потоком объекта.
- Вы должны убедиться в том, что все объекты, созданные в потоке, были разрушены прежде, чем будет разрушен объект QThread. Этого легко добиться создавая объекты в стеке в вашей реализации run().
Несмотря на то, что QObject реентерабелен, классы ГПИ, особенно QWidget и все его подклассы, таковыми не являются. Они могут использоваться только из главного потока. Как было сказано ранее, QCoreApplication::exec() также должен вызываться из главного потока.
На практике невозможно использовать классы ГПИ в других потоках, кроме главного, но выполнение продолжительных действий можно легко поместить в отдельный поток и отображать на экране результаты выполнения средствами главного потока по окончании обработки во вспомогательном потоке. Такой подход используется в примерах Мандельброт и Блокирующий клиент Fortune.
Цикл обработки событий потока
Каждый поток может иметь собственный цикл обработки событий. Главный поток начинает цикл обработки событий, используя QCoreApplication::exec(); другие потоки могут начать свои циклы обработки событий, используя QThread::exec(). Подобно QCoreApplication, QThread предоставляет функцию exit(int) и слот quit().
Цикл обработки событий потока делает возможным использование потоком некоторых неграфических классов Qt, которые требуют наличия цикла обработки событий (такие как QTimer, QTcpSocket и QProcess). Это также даёт возможность соединить сигналы из любых потоков со слотами в определённом потоке. В разделе Соединение сигналов и слотов между потоками это описано подробнее.

Экземпляр QObject считается живущим в потоке, в котором он был создан. События этому объекту пересылаются циклом обработки событий потока. Поток, в котором живет QObject, может быть получен с помощью QObject::thread().
Помните, что для объектов QObject, которые созданы до QApplication, QObject::thread() возвратит ноль. Это означает, что только главный поток может обрабатывать события для этих объектов; для объектов без потока обработка событий не происходит. Используйте функцию QObject::moveToThread() для изменения принадлежности к потоку объекта и его детей (объект не может быть перемещён, если у него есть родитель).
Calling delete on a QObject from a thread other than the one that owns the object (or accessing the object in other ways) is unsafe, unless you guarantee that the object isn't processing events at that moment. Use QObject::deleteLater() instead, and a DeferredDelete event will be posted, which the event loop of the object's thread will eventually pick up. By default, the thread that owns a QObject is the thread that creates the QObject, but not after QObject::moveToThread() has been called.
Если никакой цикл обработки событий не запущен, то события не будут доставлены объекту. Например, если вы создаёте объект QTimer в потоке, который никогда не вызывает exec(), то QTimer никогда не испустит сигнал timeout(). Вызов deleteLater() также не сработает. (Эти ограничения относятся также и к главному потоку.)
Вы можете вручную послать событие любому объекту в любом потоке используя потокобезопасную функцию QCoreApplication::postEvent(). События будут автоматически посланы циклу обработки событий потока, в котором объект был создан.
Фильтры событий поддерживаются во всех потоках, с условием, что контролируемый объект должен располагаться в том же потоке, что и контролирующий объект. Аналогично, QCoreApplication::sendEvent() (в отличие от postEvent()) может использоваться только для отправки событий объектам, живущим в том же потоке, что и посылающая события функции.
Организация доступа к подклассам QObject из других потоков
QObject и все его подклассы не потокобезопасны. Это влияет на всю систему доставки событий. Важно помнить, что цикл обработки событий может доставлять события вашему подклассу QObject в то время, как вы обращаетесь к объекту из другого потока.
Если вы вызываете функцию подкласса QObject, не живущего в текущем потоке, и объект может получать события, то вы должны защитить все обращения к данным вашего подкласса QObject с помощью мьютекса; в противном случае вы можете получить крах программы или другое неожиданное поведение.
Подобно другим объектам, QThread "живет" в потоке, в котором он был создан - не в потоке, который создан при вызове QThread::run(). Вообще, опасно иметь слоты в вашем подклассе QThread, если вы не защищаете переменные-члены с помощью мьютекса.
С другой стороны, вы можете спокойно испускать сигналы вашей реализацией QThread::run(), потому что испускание сигналов потокобезопасно.
Соединение сигналов и слотов между потоками
Qt supports these signal-slot connection types:
- Auto Connection (default) The behavior is the same as the Direct Connection, if the emitter and receiver are in the same thread. The behavior is the same as the Queued Connection, if the emitter and receiver are in different threads.
- Direct Connection The slot is invoked immediately, when the signal is emitted. The slot is executed in the emitter's thread, which is not necessarily the receiver's thread.
- Queued Connection The slot is invoked when control returns to the event loop of the receiver's thread. Слот выполняется в потоке получателя.
- Blocking Queued Connection The slot is invoked as for the Queued Connection, except the current thread blocks until the slot returns. Note: Using this type to connect objects in the same thread will cause deadlock.
- Unique Connection The behavior is the same as the Auto Connection, but the connection is made only if it does not duplicate an existing connection. i.e., if the same signal is already connected to the same slot for the same pair of objects, then the connection is not made and connect() returns false.
Это можно изменить, передав дополнительный аргумент в connect(). Помните, что использование прямых связей, когда отправитель и получатель "живут" в разных потоках, опасно в случае, если цикл обработки событий выполняется в потоке, где "живет" приемник, по той же самой причине, по которой небезопасен вызов функций объекта, принадлежащего другому потоку.
QObject::connect() сама по себе потокобезопасна.
В примере Мандельброт используется соединение с постановкой в очередь для установки связи между рабочим и основным потоками. Для того, чтобы избежать "заморозки" цикла обработки событий основного потока (и, как следствие, "заморозки" пользовательского интерфейса приложения), все рекурсивные вычисления фрактала Мандельброта выполняются в отдельном потоке. Этот поток испускает сигнал после окончания вычислений, который рисует фрактал.
Точно также, в примере Блокирующий клиент Fortune используется отдельный поток для асинхронной связи с TCP-сервером.
[Previous: Reentrancy and Thread-Safety]
[Thread Support in Qt]
[Next: Concurrent Programming]
| Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) |
Торговые марки |
Qt 4.6.4 |
|