[Предыдущая: Реентерабельность и потокобезопасность] [Поддержка потоков в Qt] [Следующая: Параллельное программирование]
Потоки и объекты QObject
QThread унаследован от QObject. Он испускает сигналы, сообщающие о том, что поток начал или закончил работу, а также предоставляет несколько слотов.
Более интересным является то, что объекты QObject могут использоваться в многопоточном приложении, испускать сигналы, приходящие в слоты, находящиеся в других потоках, и посылать события объектам, "живущим" в других потоках. Это возможно, потому что каждый поток имеет собственный цикл обработки событий.
Темы:
Реентерабельность QObject
QObject реентерабелен. Большинство из его неграфических (non-GUI) подклассов, таких как QTimer, QTcpSocket, QUdpSocket, QFtp и QProcess, также реентерабельны, что делает возможным их использование из нескольких потоков одновременно. Заметим, что эти классы спроектированы для создания и использования в одном потоке; создание объекта в одном потоке и вызов его функций из другого может и не сработать. Вы должны знать о трёх ограничениях:
- Дочерний объект QObject должен всегда создаваться в том же потоке, что и родительский объект. Кроме всего прочего, это подразумевает, что вы не должны передавать объект QThread (this) как родителя объекта, созданного в своём потоке (так как объект QThread сам создан в другом потоке).
- Объекты, управляемые событиями, могут быть использованы только в одном потоке. В частности, это относится к механизму таймера и модулю работы с сетью. Например, вы не можете запустить таймер или присоединить сокет к потоку, который не является потоком объекта.
- Вы должны убедиться в том, что все объекты, созданные в потоке, были удалены прежде, чем будет удален объект QThread. Этого легко добиться, создавая объекты в стеке в вашей реализации run().
Несмотря на то, что QObject реентерабелен, классы ГПИ, особенно QWidget и все его подклассы, таковыми не являются. Они могут использоваться только из главного потока. Как было сказано ранее, QCoreApplication::exec() также должен вызываться из главного потока.
На практике невозможно использовать классы ГПИ в других потоках, кроме главного, но выполнение продолжительных действий можно легко поместить в отдельный поток и отображать на экране результаты выполнения средствами главного потока по окончании обработки во вспомогательном потоке. Такой подход используется в примерах "Mandelbrot" и "Blocking Fortune Client".
Цикл обработки событий потока
Каждый поток может иметь собственный цикл обработки событий. Главный поток начинает цикл обработки событий, используя QCoreApplication::exec(); другие потоки могут начать свои циклы обработки событий, используя QThread::exec(). Подобно QCoreApplication, QThread предоставляет функцию exit(int) и слот quit().
Цикл обработки событий потока делает возможным использование потоком некоторых неграфических классов Qt, которые требуют наличия цикла обработки событий (такие как QTimer, QTcpSocket и QProcess). Это также даёт возможность соединить сигналы из любых потоков со слотами в определённом потоке. В разделе Соединение сигналов и слотов между потоками это описано подробнее.
Экземпляр QObject считается живущим в потоке, в котором он был создан. События этому объекту пересылаются циклом обработки событий потока. Поток, в котором живет QObject, может быть получен с помощью QObject::thread().
Помните, что для объектов QObject, которые созданы до QApplication, 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 поддерживает следующие типы соединений сигнал-слот:
- Автоматическое соединение (Auto Connection) (по умолчанию) Поведение такое же, как и при прямом соединении, если источник и получатель находятся в одном и том же потоке. Поведение такое же, как и при соединении через очередь, если источник и получатель находятся в разных потоках.
- Прямое соединение (Direct Connection) Слот вызывается немедленно при отправке сигнала. Слот выполняется в потоке отправителя, который не обязательно является потоком-получателем.
- Соединение через очередь (Queued Connection) Слот вызывается, когда управление возвращается в цикл обработки событий в потоке получателя. Слот выполняется в потоке получателя.
- Блокирующее соединение через очередь (Blocking Queued Connection) Слот вызывается так же, как и при соединении через очередь, за исключением того, что текущий поток блокируется до тех пор, пока слот не возвратит управление. Замечание: Использование этого типа подключения объектов в одном потоке приведет к взаимной блокировке.
- Уникальное соединение (Unique Connection) Поведение такое же, что и при автоматическом соединении, но соединение устанавливается только если оно не дублирует уже существующее соединение. т.е., если тот же сигнал уже соединён с тем же самым слотом для той же пары объектов, то соединение не будет установлено и connect() вернет false.
Это можно изменить, передав дополнительный аргумент в connect(). Помните, что использование прямых соединений, когда отправитель и получатель "живут" в разных потоках, опасно в случае, если цикл обработки событий выполняется в потоке, где "живет" приемник, по той же самой причине, по которой небезопасен вызов функций объекта, принадлежащего другому потоку.
QObject::connect() сама по себе потокобезопасна.
В примере "Mandelbrot" используется соединение через очередь для установки связи между рабочим и основным потоками. Для того, чтобы избежать "заморозки" цикла обработки событий основного потока (и, как следствие, "заморозки" пользовательского интерфейса приложения), все рекурсивные вычисления фрактала Мандельброта выполняются в отдельном потоке. Поток испускает сигнал, когда он заканчивает рисовать фрактал.
Точно также, в примере "Blocking Fortune Client" используется отдельный поток для асинхронной связи с TCP-сервером.
[Предыдущая: Реентерабельность и потокобезопасность] [Поддержка потоков в Qt] [Следующая: Параллельное программирование]
Авторские права © 2010 Nokia Corporation и/или её дочерние компании |
Торговые марки |
Qt 4.6.4 |
|