17.3. Работа с классами Qt вне главного потока.

Функция называется потоко-безопасной (thread-safe), когда она может свободно вызываться из нескольких потоков одновременно. Если две потоко-безопасные функции, вызываемые из разных потоков одновременно, работают с одними и теми же данными, то результат выполнения таких функций всегда предсказуем. Распространив это определение на классы можно сказать, что класс является потоко-безопасным, когда все его методы могут одновременно вызываться из нескольких потоков, без появления непредсказуемых побочных эффектов, даже если они взаимодействуют с оним и тем же объектом.

Среди потоко-безопасных классов в Qt можно назвать: QThread, QMutex, QMutexLocker, QSemaphore, QThreadStorage<T> и QWaitCondition. Кроме того, следующие функции-члены являются потоко-безопасными: QApplication::postEvent(), QApplication::removePostedEvent(), QApplication::removePostedEvents() и QEventLoop::wakeUp().

Большинство невизуальных классов Qt соответствуют менее строгому требованию -- реентерабельности. Класс называется реентерабельным, если он допускает одновременное существование нескольких экземпляров в различных потоках. Однако, одновременный доступ к реентерабельным объектам из нескольких потоков может оказаться далеко не безопасен и потому должен выполняться под защитой мьютексов. Как правило, любой класс C++, который не использует глобальные или иные разделяемые данные, является реентерабельным.

Класс QObject -- реентерабельный, но ни один из его потомков в Qt не является таковым. Как следствие -- мы не можем напрямую обращаться к виджетам вне контекста главного потока приложения. Если, скажем, нужно изменить текст в QLabel из второстепенного потока, то необходимо послать нестандартное событие в главный поток, посредством которого изменить текст надписи.

Операция удаления объекта QObject, с помощью delete, не является реентерабельной. Поэтому, при необходимости удаления объекта QObject из другого потока, нам придется вызвать метод QObject::deleteLater(), который посылает событие "deferred delete".

В контексте любого потока допускается использование механизма сигналов и слотов. При выдаче сигнала, связанный с ним слот исполняется в контексте того же потока, а не в потоке, где был создан объект-приемник. Таким образом сигналы и слоты не могут использоваться для организации взаимодействий между потоками.

Класс QTimer, и классы для работы с сетью QFtp, QHttp, QSocket и QSocketNotifier, целиком зависят от цикла обработки событий, поэтому они не могут использоваться за пределами главного потока. Единственный сетевой класс, который не зависит от цикла обработки событий -- это QSocketDevice, являющийся "оберткой" вокруг платформо-зависимого сетевого API. Некоторые программисты считают, что использование QSocketDevice в синхронном режиме, вне контекста главного потока, дает более простой код, нежели использование QSocket (который работает асинхронно), а благодаря работе вне главного потока -- он не блокирует цикл обработки событий.

Модули SQL и OpenGL так же могут использоваться в многопоточных приложениях, но имеют свои собственные ограничения, которые отличаются от системы к системе. За более подробной информацией обращайтесь по адресу: http://doc.trolltech.com/3.2/sql-driver.html, а так же к статье "Glimpsing the Third Dimension", в ежеквартальнике Qt Quarterly: http://doc.trolltech.com/qq/qq06-glimpsing.html.

Многие из невизуальных классов Qt, включая QImage, QString и другие, используют явные и неявные методы оптимизации, связанные с разделением данных между объектами. Эти классы являются реентерабельными, за исключением конструкторов копирования и операторов присваивания. Когда создается копия объекта, то копируются только указатели на данные. Это может привести к непредсказуемым последствиям, если копии объекта попытаются одновременно, из нескольких потоков, изменить данные. В подобных ситуациях можно прибегнуть к услугам класса QDeepCopy<T>, например:

QString password; QMutex mutex; void setPassword(const QString &str) { mutex.lock(); password = QDeepCopy<QString>(str); mutex.unlock(); } Возможно, Qt 4 будет иметь более широкую поддержку потоков. Так, среди всего прочего ожидается, что механизм сигналов и слотов будет расширен до поддержки установления связей через границы потоков, и позволит отказаться от необходимости создания нестандартных событий для взаимодействия с главным потоком. Ожидается так же, что невизуальные классы, подобные QSocket и QTimer, смогут использоваться вне контекста главного потока, и что необходимость в использовании класса QDeepCopy<T> отпадет.