Qt:Документация 4.3.2/threads

Материал из Wiki.crossplatform.ru

(Различия между версиями)
Перейти к: навигация, поиск
(поправил путь к картинке)
Строка 11: Строка 11:
Qt включает следующие потоковые классы:
Qt включает следующие потоковые классы:
-
*[[Qt:4.3.2/qthread | QThread]] предоставляет средства для создания нового потока.
+
*[[Qt:Документация_4.3.2/qthread | QThread]] предоставляет средства для создания нового потока.
-
*[[Qt:4.3.2/qthreadstorage | QThreadStorage]] обеспечивает хранение данных потока.
+
*[[Qt:Документация_4.3.2/qthreadstorage | QThreadStorage]] обеспечивает хранение данных потока.
-
*[[Qt:4.3.2/qmutex | QMutex]] обеспечивает защиту от взаимных блокоровок - мьютекс.
+
*[[Qt:Документация_4.3.2/qmutex | QMutex]] обеспечивает защиту от взаимных блокоровок - мьютекс.
-
*[[Qt:4.3.2/qmutexlocker | QMutexLocker]] предсоставлен для удобства, автоматически блокирует и разблокирует [[Qt:4.3.2/qmutex | QMutex]].
+
*[[Qt:Документация_4.3.2/qmutexlocker | QMutexLocker]] предсоставлен для удобства, автоматически блокирует и разблокирует [[Qt:Документация_4.3.2/qmutex | QMutex]].
-
*[[Qt:4.3.2/qreadwritelock | QReadWriteLock]] обеспечивает блокировку, позволяющую одновременное чтение.
+
*[[Qt:Документация_4.3.2/qreadwritelock | QReadWriteLock]] обеспечивает блокировку, позволяющую одновременное чтение.
-
*[[Qt:4.3.2/qreadlocker | QReadLocker]] и [[Qt:4.3.2/qwritelocker | QWriteLocker]] предоставлены для удобства, автоматически блокирует и разблокирует [[Qt:4.3.2/qreadwritelock | QReadWriteLock]].
+
*[[Qt:Документация_4.3.2/qreadlocker | QReadLocker]] и [[Qt:Документация_4.3.2/qwritelocker | QWriteLocker]] предоставлены для удобства, автоматически блокирует и разблокирует [[Qt:Документация_4.3.2/qreadwritelock | QReadWriteLock]].
-
*[[Qt:4.3.2/qsemaphore | QSemaphore]] предоставляет целочисленный семафор (обобщенный мьютекс).
+
*[[Qt:Документация_4.3.2/qsemaphore | QSemaphore]] предоставляет целочисленный семафор (обобщенный мьютекс).
-
*[[Qt:4.3.2/qwaitcondition | QWaitCondition]] предоставляет реализацию потока, который дремлет, пока не пробужен другим потоком.
+
*[[Qt:Документация_4.3.2/qwaitcondition | QWaitCondition]] предоставляет реализацию потока, который дремлет, пока не пробужен другим потоком.
===Создание потока===
===Создание потока===
-
Для создания потока, определите подкласс [[Qt:4.3.2/qthread | QThread]] и заново реализуйте его функцию [[Qt:4.3.2/qthread#run | run()]]. Например:
+
Для создания потока, определите подкласс [[Qt:Документация_4.3.2/qthread | QThread]] и заново реализуйте его функцию [[Qt:Документация_4.3.2/qthread#run | run()]]. Например:
<source lang="cpp-qt">    class MyThread : public QThread
<source lang="cpp-qt">    class MyThread : public QThread
     {
     {
Строка 33: Строка 33:
         ...
         ...
     }</source>  
     }</source>  
-
Затем, создайте экземпляр объекта вашего потокового класса и вызовите [[Qt:4.3.2/qthread#start | QThread::start]](). Код, который содержится в Вашей реализации функции [[Qt:4.3.2/qthread#run | run()]] будет выполнен в отдельном потоке. Создание потока подробно объясняется в документации [[Qt:4.3.2/qthread | QThread]].
+
Затем, создайте экземпляр объекта вашего потокового класса и вызовите [[Qt:Документация_4.3.2/qthread#start | QThread::start]](). Код, который содержится в Вашей реализации функции [[Qt:Документация_4.3.2/qthread#run | run()]] будет выполнен в отдельном потоке. Создание потока подробно объясняется в документации [[Qt:Документация_4.3.2/qthread | QThread]].
-
Обратите внимание, что [[Qt:4.3.2/qcoreapplication#exec | QCoreApplication::exec]]() всегда должен вызываться из главного потока (потока, в котором выполняется <tt>main()</tt>), а не из [[Qt:4.3.2/qthread | QThread]]. В приложениях с GUI главный поток также назвается потоком GUI thread, потому, что только ему разрешается выполнять какие-либо действия, связанные с GUI.
+
Обратите внимание, что [[Qt:Документация_4.3.2/qcoreapplication#exec | QCoreApplication::exec]]() всегда должен вызываться из главного потока (потока, в котором выполняется <tt>main()</tt>), а не из [[Qt:Документация_4.3.2/qthread | QThread]]. В приложениях с GUI главный поток также назвается потоком GUI thread, потому, что только ему разрешается выполнять какие-либо действия, связанные с GUI.
-
Кроме того, до создания объектов [[Qt:4.3.2/qthread | QThread]], вы должны создать объект [[Qt:4.3.2/qapplication | QApplication]] (или [[Qt:4.3.2/qcoreapplication | QCoreApplication]]).
+
Кроме того, до создания объектов [[Qt:Документация_4.3.2/qthread | QThread]], вы должны создать объект [[Qt:Документация_4.3.2/qapplication | QApplication]] (или [[Qt:Документация_4.3.2/qcoreapplication | QCoreApplication]]).
===Синхронизация потоков===
===Синхронизация потоков===
-
Классы [[Qt:4.3.2/qmutex | QMutex]], [[Qt:4.3.2/qreadwritelock | QReadWriteLock]], [[Qt:4.3.2/qsemaphore | QSemaphore]] и [[Qt:4.3.2/qwaitcondition | QWaitCondition]] предоставляют средства синхронизации потоков. Хотя, как основная идея потоков состоит в том, чтобы сделать потоки настолько параллельными, насколько это возможно, бывают моменты, когда поток должет остановить выполнение текущих операций и подождать другие потоки.Например, если два потока одновременно пытаются получить доступ к одной глобальной переменной, то результат, обычно, не определен.
+
Классы [[Qt:Документация_4.3.2/qmutex | QMutex]], [[Qt:Документация_4.3.2/qreadwritelock | QReadWriteLock]], [[Qt:Документация_4.3.2/qsemaphore | QSemaphore]] и [[Qt:Документация_4.3.2/qwaitcondition | QWaitCondition]] предоставляют средства синхронизации потоков. Хотя, как основная идея потоков состоит в том, чтобы сделать потоки настолько параллельными, насколько это возможно, бывают моменты, когда поток должет остановить выполнение текущих операций и подождать другие потоки.Например, если два потока одновременно пытаются получить доступ к одной глобальной переменной, то результат, обычно, не определен.
-
[[Qt:4.3.2/qmutex | QMutex]] предоставляет взаимноисключающую блокировку или мьютекс. В одно и то-же время не больше одного потока может блокировать мьютекс. Если поток пытается заблокировать мьютекс в то время, как он уже заблокирован, то поток переходит в режим ожидания пока заблокировавший мютекс поток не освободит его (мьютекс). Мьютексы часто используются для защиты доступа к разделенным данным (т.е. данным, к которым можно обратиться от нескольких потоков одновременно). В разделе [[#reentrancy-and-thread-safety | Однопоточность и Потоковая Безопасность]] ниже, мы используем мьютексы для создания потокобезопасного класса.
+
[[Qt:Документация_4.3.2/qmutex | QMutex]] предоставляет взаимноисключающую блокировку или мьютекс. В одно и то-же время не больше одного потока может блокировать мьютекс. Если поток пытается заблокировать мьютекс в то время, как он уже заблокирован, то поток переходит в режим ожидания пока заблокировавший мютекс поток не освободит его (мьютекс). Мьютексы часто используются для защиты доступа к разделенным данным (т.е. данным, к которым можно обратиться от нескольких потоков одновременно). В разделе [[#reentrancy-and-thread-safety | Однопоточность и Потоковая Безопасность]] ниже, мы используем мьютексы для создания потокобезопасного класса.
-
[[Qt:4.3.2/qreadwritelock | QReadWriteLock]] подобен [[Qt:4.3.2/qmutex | QMutex]], за исключением того, что делает различие между доступом к данным для &quot;чтения&quot; и &quot;записи&quot; и позволяет нескольким читателям одновременно обращаться к данным. По возможности используя [[Qt:4.3.2/qreadwritelock | QReadWriteLock]] вместо [[Qt:4.3.2/qmutex | QMutex]] можно сделать многопоточную программу более параллельными.
+
[[Qt:Документация_4.3.2/qreadwritelock | QReadWriteLock]] подобен [[Qt:Документация_4.3.2/qmutex | QMutex]], за исключением того, что делает различие между доступом к данным для &quot;чтения&quot; и &quot;записи&quot; и позволяет нескольким читателям одновременно обращаться к данным. По возможности используя [[Qt:Документация_4.3.2/qreadwritelock | QReadWriteLock]] вместо [[Qt:Документация_4.3.2/qmutex | QMutex]] можно сделать многопоточную программу более параллельными.
-
[[Qt:4.3.2/qsemaphore | QSemaphore]] - это обобщение для [[Qt:4.3.2/qmutex | QMutex]] которое защищает некоторое количество идентичных ресурсов. В отличие от мьютекса, защищающего один ресурс. Пример в описании [[Qt:4.3.2/threads-semaphores | Семафоров]] показывает типичное использование семафоров: синхронизированние доступа производителя и потребителя к кольцевому буферу.
+
[[Qt:Документация_4.3.2/qsemaphore | QSemaphore]] - это обобщение для [[Qt:Документация_4.3.2/qmutex | QMutex]] которое защищает некоторое количество идентичных ресурсов. В отличие от мьютекса, защищающего один ресурс. Пример в описании [[Qt:Документация_4.3.2/threads-semaphores | Семафоров]] показывает типичное использование семафоров: синхронизированние доступа производителя и потребителя к кольцевому буферу.
-
[[Qt:4.3.2/qwaitcondition | QWaitCondition]] позволяет потоку пробуждать другие потоки при выполнении некоторого условия. Один или несколько потоков могут быть заблокированы в ожидании выполнения [[Qt:4.3.2/qwaitcondition | QWaitCondition]] установленного в состояние [[Qt:4.3.2/qwaitcondition#wakeOne | wakeOne()]] или [[Qt:4.3.2/qwaitcondition#wakeAll | wakeAll()]]. При использовании [[Qt:4.3.2/qwaitcondition#wakeOne | wakeOne()]] потоки пробуждаются при выполнении одного из нужных условий, а [[Qt:4.3.2/qwaitcondition#wakeAll | wakeAll()]] требует выполнения всех условий. Пример [[Qt:4.3.2/threads-waitconditions | Условие Ожидания]] показывает как решить проблему производитель-потребитель используя [[Qt:4.3.2/qwaitcondition | QWaitCondition]] вместо [[Qt:4.3.2/qsemaphore | QSemaphore]].
+
[[Qt:Документация_4.3.2/qwaitcondition | QWaitCondition]] позволяет потоку пробуждать другие потоки при выполнении некоторого условия. Один или несколько потоков могут быть заблокированы в ожидании выполнения [[Qt:Документация_4.3.2/qwaitcondition | QWaitCondition]] установленного в состояние [[Qt:Документация_4.3.2/qwaitcondition#wakeOne | wakeOne()]] или [[Qt:Документация_4.3.2/qwaitcondition#wakeAll | wakeAll()]]. При использовании [[Qt:Документация_4.3.2/qwaitcondition#wakeOne | wakeOne()]] потоки пробуждаются при выполнении одного из нужных условий, а [[Qt:Документация_4.3.2/qwaitcondition#wakeAll | wakeAll()]] требует выполнения всех условий. Пример [[Qt:Документация_4.3.2/threads-waitconditions | Условие Ожидания]] показывает как решить проблему производитель-потребитель используя [[Qt:Документация_4.3.2/qwaitcondition | QWaitCondition]] вместо [[Qt:Документация_4.3.2/qsemaphore | QSemaphore]].
==Монопоточность и потоковая безопасность==
==Монопоточность и потоковая безопасность==
Строка 79: Строка 79:
Потоки A и B одновременно могут загрузить старое значение переменной, увеличить ее значение в регистре и сохранить значение переменной в памяти, но переменная будет увеличена только однажны!
Потоки A и B одновременно могут загрузить старое значение переменной, увеличить ее значение в регистре и сохранить значение переменной в памяти, но переменная будет увеличена только однажны!
-
Становится ясно, что обращения должны быть упорядочены: Поток A должен выполнить шаги 1, 2, 3 без прерывания (автоматического) прежде, чем поток B сможет выполнить теже шаги; или наоборот. Самый легкий способ создания потокобезопасного класса состоит в том, чтобы защитить весь доступ к членам с помощью [[Qt:4.3.2/qmutex | QMutex]]:
+
Становится ясно, что обращения должны быть упорядочены: Поток A должен выполнить шаги 1, 2, 3 без прерывания (автоматического) прежде, чем поток B сможет выполнить теже шаги; или наоборот. Самый легкий способ создания потокобезопасного класса состоит в том, чтобы защитить весь доступ к членам с помощью [[Qt:Документация_4.3.2/qmutex | QMutex]]:
<source lang="cpp-qt">    class Counter
<source lang="cpp-qt">    class Counter
     {
     {
Строка 93: Строка 93:
         int n;
         int n;
     };</source>  
     };</source>  
-
Класс [[Qt:4.3.2/qmutexlocker | QMutexLocker]] автоматически запирает мьютекс в своем конструкторе и отпирает его в деструкторе, вызываемом при завершении функции. Запирание мьютекса гарантирует, что обращения из разных потоков будут упорядочены. Член <tt>mutex</tt> объявлен как <tt>mutable</tt> потому, что позволяет запереть и отпереть мьютекс в функции <tt>value()</tt>, которая является константной.
+
Класс [[Qt:Документация_4.3.2/qmutexlocker | QMutexLocker]] автоматически запирает мьютекс в своем конструкторе и отпирает его в деструкторе, вызываемом при завершении функции. Запирание мьютекса гарантирует, что обращения из разных потоков будут упорядочены. Член <tt>mutex</tt> объявлен как <tt>mutable</tt> потому, что позволяет запереть и отпереть мьютекс в функции <tt>value()</tt>, которая является константной.
-
Большинство классов Qt сделаны межпоточными и не потокобезопасными для того, чтобы избежать многократного запирания и отпирания [[Qt:4.3.2/qmutex | QMutex]]. Например, класс [[Qt:4.3.2/qstring | QString]] является межпоточным, что означает, что вы можете использовать его в различных потоках, но вы не можете получить доступ к одному и тому-же объекту [[Qt:4.3.2/qstring | QString]] одновременно из различных потоков (если вы самостоятельно не защищаете его с помощью мьютекса). Несколько классов и функций являются потокобезопасными; они, главным образом, связаны с потоковыми классами, такими как [[Qt:4.3.2/qmutex | QMutex]], или фундаментальными функциями, такими как [[Qt:4.3.2/qcoreapplication#postEvent | QCoreApplication::postEvent]]().
+
Большинство классов Qt сделаны межпоточными и не потокобезопасными для того, чтобы избежать многократного запирания и отпирания [[Qt:Документация_4.3.2/qmutex | QMutex]]. Например, класс [[Qt:Документация_4.3.2/qstring | QString]] является межпоточным, что означает, что вы можете использовать его в различных потоках, но вы не можете получить доступ к одному и тому-же объекту [[Qt:Документация_4.3.2/qstring | QString]] одновременно из различных потоков (если вы самостоятельно не защищаете его с помощью мьютекса). Несколько классов и функций являются потокобезопасными; они, главным образом, связаны с потоковыми классами, такими как [[Qt:Документация_4.3.2/qmutex | QMutex]], или фундаментальными функциями, такими как [[Qt:Документация_4.3.2/qcoreapplication#postEvent | QCoreApplication::postEvent]]().
==Потоки и объекты QObject==
==Потоки и объекты QObject==
-
[[Qt:4.3.2/qthread | QThread]] наследует [[Qt:4.3.2/qobject | QObject]]. Он испускает сигналы, сообщающие о том, что поток начал или закончил работу, а также предоставляет несколько слотов.
+
[[Qt:Документация_4.3.2/qthread | QThread]] наследует [[Qt:Документация_4.3.2/qobject | QObject]]. Он испускает сигналы, сообщающие о том, что поток начал или закончил работу, а также предоставляет несколько слотов.
-
Очень интересно использование [[Qt:4.3.2/qobject | QObject]] в многопоточном приложении, он испускает сигналы, приходящие в слоты, находящиеся в других потоках, и посылает сообщения объектам &quot;живущим&quot; в других потоках. Это возможно потому, что каждый поток имеет собственный цикл обработки сообщений.
+
Очень интересно использование [[Qt:Документация_4.3.2/qobject | QObject]] в многопоточном приложении, он испускает сигналы, приходящие в слоты, находящиеся в других потоках, и посылает сообщения объектам &quot;живущим&quot; в других потоках. Это возможно потому, что каждый поток имеет собственный цикл обработки сообщений.
===Монопоточность QObject===
===Монопоточность QObject===
-
[[Qt:4.3.2/qobject | QObject]] монопоточен. Большинство из его не-GUI подклассов, таких как [[Qt:4.3.2/qtimer | QTimer]], [[Qt:4.3.2/qtcpsocket | QTcpSocket]], [[Qt:4.3.2/qudpsocket | QUdpSocket]], [[Qt:4.3.2/qhttp | QHttp]], [[Qt:4.3.2/qftp | QFtp]] и [[Qt:4.3.2/qprocess | QProcess]], также межпоточны. Их возможно использовать из нескольких потоков одновременно. Вы должны знать о двух ограничениях:
+
[[Qt:Документация_4.3.2/qobject | QObject]] монопоточен. Большинство из его не-GUI подклассов, таких как [[Qt:Документация_4.3.2/qtimer | QTimer]], [[Qt:Документация_4.3.2/qtcpsocket | QTcpSocket]], [[Qt:Документация_4.3.2/qudpsocket | QUdpSocket]], [[Qt:Документация_4.3.2/qhttp | QHttp]], [[Qt:Документация_4.3.2/qftp | QFtp]] и [[Qt:Документация_4.3.2/qprocess | QProcess]], также межпоточны. Их возможно использовать из нескольких потоков одновременно. Вы должны знать о двух ограничениях:
-
*''Дочерние по отношению к [[Qt:4.3.2/qobject | QObject]] объекты должны создаваться с том же потоке, что и родитель.'' Это подразумевает, помимо прочего, что вы никогда не должны передавать объект [[Qt:4.3.2/qthread | QThread]] (<tt>this</tt>) как родитель объекта, созданного в своем потоке (так как объект [[Qt:4.3.2/qthread | QThread]] сам создан в другом потоке).
+
*''Дочерние по отношению к [[Qt:Документация_4.3.2/qobject | QObject]] объекты должны создаваться с том же потоке, что и родитель.'' Это подразумевает, помимо прочего, что вы никогда не должны передавать объект [[Qt:Документация_4.3.2/qthread | QThread]] (<tt>this</tt>) как родитель объекта, созданного в своем потоке (так как объект [[Qt:Документация_4.3.2/qthread | QThread]] сам создан в другом потоке).
-
*''Вы должны заботиться о том, чтобы все объекты, созданные в потоке, были разрушены прежде, чем будет разрушен объект [[Qt:4.3.2/qthread | QThread]].'' Это легко можно сделать создавая объекты в стеке в Вашей реализации [[Qt:4.3.2/qthread#run | run()]].
+
*''Вы должны заботиться о том, чтобы все объекты, созданные в потоке, были разрушены прежде, чем будет разрушен объект [[Qt:Документация_4.3.2/qthread | QThread]].'' Это легко можно сделать создавая объекты в стеке в Вашей реализации [[Qt:Документация_4.3.2/qthread#run | run()]].
-
Несмотря на то, что [[Qt:4.3.2/qobject | QObject]] межпоточен, классы GUI, особенно [[Qt:4.3.2/qwidget | QWidget]] и все его подклассы, не межпоточны. Они могут использоваться только в главном потоке. Как отмечено выше, [[Qt:4.3.2/qcoreapplication#exec | QCoreApplication::exec]]() также может вызываться из главного потока.
+
Несмотря на то, что [[Qt:Документация_4.3.2/qobject | QObject]] межпоточен, классы GUI, особенно [[Qt:Документация_4.3.2/qwidget | QWidget]] и все его подклассы, не межпоточны. Они могут использоваться только в главном потоке. Как отмечено выше, [[Qt:Документация_4.3.2/qcoreapplication#exec | QCoreApplication::exec]]() также может вызываться из главного потока.
-
На практике невозможно использовать классы GUI в других потоках, кроме главного, но можен легко поместить выполнение продолжительных действий в отдельный поток и отображать на экране результаты выполнения средствами главного потока по окончании обработки. Такой подход используется в примерах [[Qt:4.3.2/threads-mandelbrot | Mandelbrot]] и [[Qt:4.3.2/network-blockingfortuneclient | Blocking Fortune Client]].
+
На практике невозможно использовать классы GUI в других потоках, кроме главного, но можен легко поместить выполнение продолжительных действий в отдельный поток и отображать на экране результаты выполнения средствами главного потока по окончании обработки. Такой подход используется в примерах [[Qt:Документация_4.3.2/threads-mandelbrot | Mandelbrot]] и [[Qt:Документация_4.3.2/network-blockingfortuneclient | Blocking Fortune Client]].
===Цикл обработки сообщений потока===
===Цикл обработки сообщений потока===
-
Каждый поток может иметь собственный цикл обработки сообщений. Главный поток начинает цикл обработки сообщений используя [[Qt:4.3.2/qcoreapplication#exec | QCoreApplication::exec]](); другие потоки могут начать свои циклы обработки сообщений используя [[Qt:4.3.2/qthread#exec | QThread::exec]](). Подобно [[Qt:4.3.2/qcoreapplication | QCoreApplication]], [[Qt:4.3.2/qthread | QThread]] предоставляет функцию [[Qt:4.3.2/qthread#exit | exit]](int) и слот [[Qt:4.3.2/qthread#quit | quit()]].
+
Каждый поток может иметь собственный цикл обработки сообщений. Главный поток начинает цикл обработки сообщений используя [[Qt:Документация_4.3.2/qcoreapplication#exec | QCoreApplication::exec]](); другие потоки могут начать свои циклы обработки сообщений используя [[Qt:Документация_4.3.2/qthread#exec | QThread::exec]](). Подобно [[Qt:Документация_4.3.2/qcoreapplication | QCoreApplication]], [[Qt:Документация_4.3.2/qthread | QThread]] предоставляет функцию [[Qt:Документация_4.3.2/qthread#exit | exit]](int) и слот [[Qt:Документация_4.3.2/qthread#quit | quit()]].
-
Цикл обработки сообщений сделан возможным для потока, чтобы можно было использовать некоторые не-GUI классы Qt которые требуют наличия цикла обработки сообщений (такие как [[Qt:4.3.2/qtimer | QTimer]], [[Qt:4.3.2/qtcpsocket | QTcpSocket]] и [[Qt:4.3.2/qprocess | QProcess]]). Это также доет возможность соединить сигналы из любых потоков со слотами в определенном потоке. В разделе [[#signals-and-slots-across-threads | Соединение Сигналов и Слотов Между Потоками]] это описано подробнее.
+
Цикл обработки сообщений сделан возможным для потока, чтобы можно было использовать некоторые не-GUI классы Qt которые требуют наличия цикла обработки сообщений (такие как [[Qt:Документация_4.3.2/qtimer | QTimer]], [[Qt:Документация_4.3.2/qtcpsocket | QTcpSocket]] и [[Qt:Документация_4.3.2/qprocess | QProcess]]). Это также доет возможность соединить сигналы из любых потоков со слотами в определенном потоке. В разделе [[#signals-and-slots-across-threads | Соединение Сигналов и Слотов Между Потоками]] это описано подробнее.
[[Image:threadsandobjects.png]]
[[Image:threadsandobjects.png]]
-
Экземпляр [[Qt:4.3.2/qobject | QObject]] считается ''живущим'' в потоке, в котором был создан. Сообщения этому объекту пересылаются циклом обработки сообщений потока. Поток в котором живет [[Qt:4.3.2/qobject | QObject]] может быть получен с помощью [[Qt:4.3.2/qobject#thread | QObject::thread]]().
+
Экземпляр [[Qt:Документация_4.3.2/qobject | QObject]] считается ''живущим'' в потоке, в котором был создан. Сообщения этому объекту пересылаются циклом обработки сообщений потока. Поток в котором живет [[Qt:Документация_4.3.2/qobject | QObject]] может быть получен с помощью [[Qt:Документация_4.3.2/qobject#thread | QObject::thread]]().
-
Вызов <tt>delete</tt> для объекта [[Qt:4.3.2/qobject | QObject]] (и вообще обращение к объекту) из потока, отличного от того, в котором он был создан может быть опасен, если нельзя быть уверенным, что объект не обрабатывает другие сообщения в этот момент. Вместо этого используйте [[Qt:4.3.2/qobject#deleteLater | QObject::deleteLater]](); объекту будет послано сообщение [[Qt:4.3.2/qevent#Type-enum | DeferredDelete]], которое, в конце концов, будет обработано циклом обработки сообщений данного объекта.
+
Вызов <tt>delete</tt> для объекта [[Qt:Документация_4.3.2/qobject | QObject]] (и вообще обращение к объекту) из потока, отличного от того, в котором он был создан может быть опасен, если нельзя быть уверенным, что объект не обрабатывает другие сообщения в этот момент. Вместо этого используйте [[Qt:Документация_4.3.2/qobject#deleteLater | QObject::deleteLater]](); объекту будет послано сообщение [[Qt:Документация_4.3.2/qevent#Type-enum | DeferredDelete]], которое, в конце концов, будет обработано циклом обработки сообщений данного объекта.
-
Если никакой цикл обработки сообщений не запущен, то сообщения не будут доставлены объекту. Например, если вы создаете объект [[Qt:4.3.2/qtimer | QTimer]] в потоке, который никогда не вызывает [[Qt:4.3.2/qthread#exec | exec()]], то [[Qt:4.3.2/qtimer | QTimer]] никогда не испустит сигнал [[Qt:4.3.2/qtimer#timeout | timeout()]]. Вызов [[Qt:4.3.2/qobject#deleteLater | deleteLater()]] также не сработает. (Это также относится к главному потоку.)
+
Если никакой цикл обработки сообщений не запущен, то сообщения не будут доставлены объекту. Например, если вы создаете объект [[Qt:Документация_4.3.2/qtimer | QTimer]] в потоке, который никогда не вызывает [[Qt:Документация_4.3.2/qthread#exec | exec()]], то [[Qt:Документация_4.3.2/qtimer | QTimer]] никогда не испустит сигнал [[Qt:Документация_4.3.2/qtimer#timeout | timeout()]]. Вызов [[Qt:Документация_4.3.2/qobject#deleteLater | deleteLater()]] также не сработает. (Это также относится к главному потоку.)
-
Вы можете вручную послать сообщение любому объекту в любом потоке используя потокобезопасную функцию [[Qt:4.3.2/qcoreapplication#postEvent | QCoreApplication::postEvent]](). Сообщения будут автоматически посланы циклу обработки сообщений потока, в котором объект был создан.
+
Вы можете вручную послать сообщение любому объекту в любом потоке используя потокобезопасную функцию [[Qt:Документация_4.3.2/qcoreapplication#postEvent | QCoreApplication::postEvent]](). Сообщения будут автоматически посланы циклу обработки сообщений потока, в котором объект был создан.
-
Фильтры сообщений поддерживаются во всех потоках, с условием, что контролируемый объект должен располагаться в том-же потоке, что и контролирующий объект. Также [[Qt:4.3.2/qcoreapplication#sendEvent | QCoreApplication::sendEvent]]() (в отличие от [[Qt:4.3.2/qcoreapplication#postEvent | postEvent()]]) может использоваться только для посылки сообщений живущим в том-же потоке, что и посылающая сообщения функции.
+
Фильтры сообщений поддерживаются во всех потоках, с условием, что контролируемый объект должен располагаться в том-же потоке, что и контролирующий объект. Также [[Qt:Документация_4.3.2/qcoreapplication#sendEvent | QCoreApplication::sendEvent]]() (в отличие от [[Qt:Документация_4.3.2/qcoreapplication#postEvent | postEvent()]]) может использоваться только для посылки сообщений живущим в том-же потоке, что и посылающая сообщения функции.
===Вызов подклассов QObject из других потоков===
===Вызов подклассов QObject из других потоков===
-
[[Qt:4.3.2/qobject | QObject]] и все его подклассы не потокобезопасны. Это влияет на всю систему доставки сообщений. Важно помнить, что цикл обработки сообщений может доставлять сообщения Вашему подклассу [[Qt:4.3.2/qobject | QObject]] в то время, как вы обращаетесь к объекту из другого потока.
+
[[Qt:Документация_4.3.2/qobject | QObject]] и все его подклассы не потокобезопасны. Это влияет на всю систему доставки сообщений. Важно помнить, что цикл обработки сообщений может доставлять сообщения Вашему подклассу [[Qt:Документация_4.3.2/qobject | QObject]] в то время, как вы обращаетесь к объекту из другого потока.
-
Если Вы вызываете функции подкласса [[Qt:4.3.2/qobject | QObject]] не живущего в текущем потоке, и объект может получать сообщения, то вы должны защитить все обращения к данным Вашего подкласса [[Qt:4.3.2/qobject | QObject]] с помощью мьютекса; в противном случае вы можете получить крах программы или другое неожиданное поведение.
+
Если Вы вызываете функции подкласса [[Qt:Документация_4.3.2/qobject | QObject]] не живущего в текущем потоке, и объект может получать сообщения, то вы должны защитить все обращения к данным Вашего подкласса [[Qt:Документация_4.3.2/qobject | QObject]] с помощью мьютекса; в противном случае вы можете получить крах программы или другое неожиданное поведение.
-
Подобно другим объектам, объект [[Qt:4.3.2/qthread | QThread]] живет в потоке, в котором он был создан, а ''не'' в потоке, который создан при вызове [[Qt:4.3.2/qthread#run | QThread::run]](). Вообще, опасно иметь слоты в Вашем подклассе [[Qt:4.3.2/qthread | QThread]], если вы не защищаете переменные-члены с помощью мьютекс.
+
Подобно другим объектам, объект [[Qt:Документация_4.3.2/qthread | QThread]] живет в потоке, в котором он был создан, а ''не'' в потоке, который создан при вызове [[Qt:Документация_4.3.2/qthread#run | QThread::run]](). Вообще, опасно иметь слоты в Вашем подклассе [[Qt:Документация_4.3.2/qthread | QThread]], если вы не защищаете переменные-члены с помощью мьютекс.
-
С другой стороны, Вы можете спокойно испускать сигналы Вашей реализацией [[Qt:4.3.2/qthread#run | QThread::run]](), потому, что испускание сигналов потокобезопасно.
+
С другой стороны, Вы можете спокойно испускать сигналы Вашей реализацией [[Qt:Документация_4.3.2/qthread#run | QThread::run]](), потому, что испускание сигналов потокобезопасно.
===Связь сигналов и слотов между потоками===
===Связь сигналов и слотов между потоками===
Qt поддерживает два типа соединений сигнал-слот:
Qt поддерживает два типа соединений сигнал-слот:
-
*C [[Qt:4.3.2/qt#ConnectionType-enum | прямыми связями]] - слот выполняется немедленно после испускания сигнала. Слот выполняется в потоке, испустившем сигнал (который не обязательно является потоком, в котором живет объект).
+
*C [[Qt:Документация_4.3.2/qt#ConnectionType-enum | прямыми связями]] - слот выполняется немедленно после испускания сигнала. Слот выполняется в потоке, испустившем сигнал (который не обязательно является потоком, в котором живет объект).
-
*С [[Qt:4.3.2/qt#ConnectionType-enum | постановкой сигналов в очередь]] - слот выполняется, когда управление возвращается циклу обработки сообщений потока, которому принадлежит объект. Слот выполняется в потоке, в котором проживает объект-приемник.
+
*С [[Qt:Документация_4.3.2/qt#ConnectionType-enum | постановкой сигналов в очередь]] - слот выполняется, когда управление возвращается циклу обработки сообщений потока, которому принадлежит объект. Слот выполняется в потоке, в котором проживает объект-приемник.
-
По умолчанию [[Qt:4.3.2/qobject#connect | QObject::connect]]() устанавливает прямую связь, если отправитель и получатель принадлежат одному потоку, и связь с постановкой в очередь, если отправитель и получатель принадлежат различным потокам. Это можно изменить, передав дополнительный аргумент в [[Qt:4.3.2/qobject#connect | connect()]]. Помните, что использование прямых связей, когда отправитель и получатель проживают в разных потоках опасно в случае, если цикл обработки сообщений выполняется в потоке, где живет приемник, по той-же самой причине, по которой небезопасен вызов функций объекта, принадлежащего другому потоку.
+
По умолчанию [[Qt:Документация_4.3.2/qobject#connect | QObject::connect]]() устанавливает прямую связь, если отправитель и получатель принадлежат одному потоку, и связь с постановкой в очередь, если отправитель и получатель принадлежат различным потокам. Это можно изменить, передав дополнительный аргумент в [[Qt:Документация_4.3.2/qobject#connect | connect()]]. Помните, что использование прямых связей, когда отправитель и получатель проживают в разных потоках опасно в случае, если цикл обработки сообщений выполняется в потоке, где живет приемник, по той-же самой причине, по которой небезопасен вызов функций объекта, принадлежащего другому потоку.
-
[[Qt:4.3.2/qobject#connect | QObject::connect]]() само по себе потокобезопасно.
+
[[Qt:Документация_4.3.2/qobject#connect | QObject::connect]]() само по себе потокобезопасно.
-
В примере [[Qt:4.3.2/threads-mandelbrot | Mandelbrot]] используется соединение с постановкой в очередь для установки связи между рабочим и основным потоками. Для того, чтобы избежать заморозки цикла обработки сообщений основного потока (и, как следствие, заморозки пользовательского интерфейса приложения), все рекурсивные вычисления фрактала в Mandelbrot выполняются в отдельно потоке. Это поток испускает сигнал после вычислений, который рисует фрактал.
+
В примере [[Qt:Документация_4.3.2/threads-mandelbrot | Mandelbrot]] используется соединение с постановкой в очередь для установки связи между рабочим и основным потоками. Для того, чтобы избежать заморозки цикла обработки сообщений основного потока (и, как следствие, заморозки пользовательского интерфейса приложения), все рекурсивные вычисления фрактала в Mandelbrot выполняются в отдельно потоке. Это поток испускает сигнал после вычислений, который рисует фрактал.
-
Точно также, в примере [[Qt:4.3.2/network-blockingfortuneclient | Blocking Fortune Client]] используется отдельный поток для асинхронной связи с TCP-сервером.
+
Точно также, в примере [[Qt:Документация_4.3.2/network-blockingfortuneclient | Blocking Fortune Client]] используется отдельный поток для асинхронной связи с TCP-сервером.
==Потоки и неявное совместное использование данных==
==Потоки и неявное совместное использование данных==
-
Qt использует оптимизацию, называемую [[Qt:4.3.2/shared | неявным совместным использованием данных]] для многих своих классов, особенно [[Qt:4.3.2/qimage | QImage]] и [[Qt:4.3.2/qstring | QString]]. Многие считеют, что неявное совместное использование данных и много поточность - несовместимые концепции из-за способа, которым обычно выполняется подсчет ссылок. Одно из решений состоит в том, чтобы защитить внутренний счетчик ссылок с помощью мьютекса, но это очень медленно. Более ранние версии Qt не предоставляли удовленворительного решения этой проблемы.
+
Qt использует оптимизацию, называемую [[Qt:Документация_4.3.2/shared | неявным совместным использованием данных]] для многих своих классов, особенно [[Qt:Документация_4.3.2/qimage | QImage]] и [[Qt:Документация_4.3.2/qstring | QString]]. Многие считеют, что неявное совместное использование данных и много поточность - несовместимые концепции из-за способа, которым обычно выполняется подсчет ссылок. Одно из решений состоит в том, чтобы защитить внутренний счетчик ссылок с помощью мьютекса, но это очень медленно. Более ранние версии Qt не предоставляли удовленворительного решения этой проблемы.
Начиная с Qt 4, классы использующие неявное совместное использование данных могут быть безопасно скопированы между потоками подобно любым другим классам. Они полностью [[#reentrant | межпоточны]]. Неявное совместное использование данных действительно неявно. Это реализовано с использованием атомарных действий подсчета ссылок на ассемблере для различных платформ, поддерживаемых Qt. Атомарный подсчет ссылок очень быстр, намного быстрее, чем использование мьютекса.
Начиная с Qt 4, классы использующие неявное совместное использование данных могут быть безопасно скопированы между потоками подобно любым другим классам. Они полностью [[#reentrant | межпоточны]]. Неявное совместное использование данных действительно неявно. Это реализовано с использованием атомарных действий подсчета ссылок на ассемблере для различных платформ, поддерживаемых Qt. Атомарный подсчет ссылок очень быстр, намного быстрее, чем использование мьютекса.

Версия 07:02, 30 октября 2008

40px Внимание: Актуальная версия перевода документации находится здесь

__NOTOC__

Image:qt-logo.png

Главная · Все классы · Основные классы · Классы по группам · Модули · Функции

Image:trolltech-logo.png

Содержание

Поддержка потоков в Qt

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

Более ранние версии Qt предлагали возможность постройки библиотеки без поддержки потоков. Начиная с Qt 4.0 потоки всегда доступны.

Данный документ предназначен для аудитории имеющей знания и опыт работы с многопоточными приложениями. Если вы плохо знакомы с потоками, см. наш Список рекомендованной литературы.

Потоковые классы

Qt включает следующие потоковые классы:

  • QThread предоставляет средства для создания нового потока.
  • QThreadStorage обеспечивает хранение данных потока.
  • QMutex обеспечивает защиту от взаимных блокоровок - мьютекс.
  • QMutexLocker предсоставлен для удобства, автоматически блокирует и разблокирует QMutex.
  • QReadWriteLock обеспечивает блокировку, позволяющую одновременное чтение.
  • QReadLocker и QWriteLocker предоставлены для удобства, автоматически блокирует и разблокирует QReadWriteLock.
  • QSemaphore предоставляет целочисленный семафор (обобщенный мьютекс).
  • QWaitCondition предоставляет реализацию потока, который дремлет, пока не пробужен другим потоком.

Создание потока

Для создания потока, определите подкласс QThread и заново реализуйте его функцию run(). Например:

    class MyThread : public QThread
    {
        Q_OBJECT
 
    protected:
        void run();
    };
 
    void MyThread::run()
    {
        ...
    }

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

Обратите внимание, что QCoreApplication::exec() всегда должен вызываться из главного потока (потока, в котором выполняется main()), а не из QThread. В приложениях с GUI главный поток также назвается потоком GUI thread, потому, что только ему разрешается выполнять какие-либо действия, связанные с GUI.

Кроме того, до создания объектов QThread, вы должны создать объект QApplication (или QCoreApplication).

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

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

QMutex предоставляет взаимноисключающую блокировку или мьютекс. В одно и то-же время не больше одного потока может блокировать мьютекс. Если поток пытается заблокировать мьютекс в то время, как он уже заблокирован, то поток переходит в режим ожидания пока заблокировавший мютекс поток не освободит его (мьютекс). Мьютексы часто используются для защиты доступа к разделенным данным (т.е. данным, к которым можно обратиться от нескольких потоков одновременно). В разделе Однопоточность и Потоковая Безопасность ниже, мы используем мьютексы для создания потокобезопасного класса.

QReadWriteLock подобен QMutex, за исключением того, что делает различие между доступом к данным для "чтения" и "записи" и позволяет нескольким читателям одновременно обращаться к данным. По возможности используя QReadWriteLock вместо QMutex можно сделать многопоточную программу более параллельными.

QSemaphore - это обобщение для QMutex которое защищает некоторое количество идентичных ресурсов. В отличие от мьютекса, защищающего один ресурс. Пример в описании Семафоров показывает типичное использование семафоров: синхронизированние доступа производителя и потребителя к кольцевому буферу.

QWaitCondition позволяет потоку пробуждать другие потоки при выполнении некоторого условия. Один или несколько потоков могут быть заблокированы в ожидании выполнения QWaitCondition установленного в состояние wakeOne() или wakeAll(). При использовании wakeOne() потоки пробуждаются при выполнении одного из нужных условий, а wakeAll() требует выполнения всех условий. Пример Условие Ожидания показывает как решить проблему производитель-потребитель используя QWaitCondition вместо QSemaphore.

Монопоточность и потоковая безопасность

Везде в документации Qt, термины межпоточность и потоковая безопасность для определения того, как функции могут использоваться в многопоточных приложениях:

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

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

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

Большинство классов-наследников классов C++ являются межпоточными, поскольку обычно они работают с данными членов класса. Любой поток может вызвать функцию-член экземпляра класса пока другой поток не вызывает функцию-член того-же самого экземпляра класса. Например, класс Counter показанный ниже является межпоточным:

    class Counter
    {
    public:
        Counter() { n = 0; }
 
        void increment() { ++n; }
        void decrement() { --n; }
        int value() const { return n; }
 
    private:
        int n;
    };

Данный класс не является потокобезопасным, поскольку если несколько потоков попытаются изменить член n, результат будет неопределен. Это так, потому что операторы C++ ++ и -- не всегда атомарны. В действительности они обычно расширяются до трех машинных инструкций:

  1. Загрузка значения переменной в регистр.
  2. Увеличение или уменьшение значения регистра.
  3. Сохранение значения регистра обратно в основную память.

Потоки A и B одновременно могут загрузить старое значение переменной, увеличить ее значение в регистре и сохранить значение переменной в памяти, но переменная будет увеличена только однажны!

Становится ясно, что обращения должны быть упорядочены: Поток A должен выполнить шаги 1, 2, 3 без прерывания (автоматического) прежде, чем поток B сможет выполнить теже шаги; или наоборот. Самый легкий способ создания потокобезопасного класса состоит в том, чтобы защитить весь доступ к членам с помощью QMutex:

    class Counter
    {
    public:
        Counter() { n = 0; }
 
        void increment() { QMutexLocker locker(&amp;mutex); ++n; }
        void decrement() { QMutexLocker locker(&amp;mutex); --n; }
        int value() const { QMutexLocker locker(&amp;mutex); return n; }
 
    private:
        mutable QMutex mutex;
        int n;
    };

Класс QMutexLocker автоматически запирает мьютекс в своем конструкторе и отпирает его в деструкторе, вызываемом при завершении функции. Запирание мьютекса гарантирует, что обращения из разных потоков будут упорядочены. Член mutex объявлен как mutable потому, что позволяет запереть и отпереть мьютекс в функции value(), которая является константной.

Большинство классов Qt сделаны межпоточными и не потокобезопасными для того, чтобы избежать многократного запирания и отпирания QMutex. Например, класс QString является межпоточным, что означает, что вы можете использовать его в различных потоках, но вы не можете получить доступ к одному и тому-же объекту QString одновременно из различных потоков (если вы самостоятельно не защищаете его с помощью мьютекса). Несколько классов и функций являются потокобезопасными; они, главным образом, связаны с потоковыми классами, такими как QMutex, или фундаментальными функциями, такими как QCoreApplication::postEvent().

Потоки и объекты QObject

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

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

Монопоточность QObject

QObject монопоточен. Большинство из его не-GUI подклассов, таких как QTimer, QTcpSocket, QUdpSocket, QHttp, QFtp и QProcess, также межпоточны. Их возможно использовать из нескольких потоков одновременно. Вы должны знать о двух ограничениях:

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

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

На практике невозможно использовать классы GUI в других потоках, кроме главного, но можен легко поместить выполнение продолжительных действий в отдельный поток и отображать на экране результаты выполнения средствами главного потока по окончании обработки. Такой подход используется в примерах Mandelbrot и Blocking Fortune Client.

Цикл обработки сообщений потока

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

Цикл обработки сообщений сделан возможным для потока, чтобы можно было использовать некоторые не-GUI классы Qt которые требуют наличия цикла обработки сообщений (такие как QTimer, QTcpSocket и QProcess). Это также доет возможность соединить сигналы из любых потоков со слотами в определенном потоке. В разделе Соединение Сигналов и Слотов Между Потоками это описано подробнее.

Image:threadsandobjects.png

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

Вызов delete для объекта QObject (и вообще обращение к объекту) из потока, отличного от того, в котором он был создан может быть опасен, если нельзя быть уверенным, что объект не обрабатывает другие сообщения в этот момент. Вместо этого используйте QObject::deleteLater(); объекту будет послано сообщение DeferredDelete, которое, в конце концов, будет обработано циклом обработки сообщений данного объекта.

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

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

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

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

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

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

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

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

Связь сигналов и слотов между потоками

Qt поддерживает два типа соединений сигнал-слот:

  • C прямыми связями - слот выполняется немедленно после испускания сигнала. Слот выполняется в потоке, испустившем сигнал (который не обязательно является потоком, в котором живет объект).
  • С постановкой сигналов в очередь - слот выполняется, когда управление возвращается циклу обработки сообщений потока, которому принадлежит объект. Слот выполняется в потоке, в котором проживает объект-приемник.

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

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

В примере Mandelbrot используется соединение с постановкой в очередь для установки связи между рабочим и основным потоками. Для того, чтобы избежать заморозки цикла обработки сообщений основного потока (и, как следствие, заморозки пользовательского интерфейса приложения), все рекурсивные вычисления фрактала в Mandelbrot выполняются в отдельно потоке. Это поток испускает сигнал после вычислений, который рисует фрактал.

Точно также, в примере Blocking Fortune Client используется отдельный поток для асинхронной связи с TCP-сервером.

Потоки и неявное совместное использование данных

Qt использует оптимизацию, называемую неявным совместным использованием данных для многих своих классов, особенно QImage и QString. Многие считеют, что неявное совместное использование данных и много поточность - несовместимые концепции из-за способа, которым обычно выполняется подсчет ссылок. Одно из решений состоит в том, чтобы защитить внутренний счетчик ссылок с помощью мьютекса, но это очень медленно. Более ранние версии Qt не предоставляли удовленворительного решения этой проблемы.

Начиная с Qt 4, классы использующие неявное совместное использование данных могут быть безопасно скопированы между потоками подобно любым другим классам. Они полностью межпоточны. Неявное совместное использование данных действительно неявно. Это реализовано с использованием атомарных действий подсчета ссылок на ассемблере для различных платформ, поддерживаемых Qt. Атомарный подсчет ссылок очень быстр, намного быстрее, чем использование мьютекса.

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

Резюмируя скажем, что неявное совместное использование данных в Qt 4 действительно неявное разделение. Даже в многопоточных приложениях вы можете благополучно применять классы, использующие неявное разделение данных, так, будто это простые межпоточные классы.

Потоки и модуль SQL

Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается.

Кроме того, библиотеки третьих лиц, используемые драйверами QSqlDriver могут наложить дополнительные ограничения на использование Модуля SQL в многопоточной программе. За дополнительной информацией обращайтесь к создателю клиента базы данных.

Рекомендуемая литература


Copyright © 2007 Trolltech Trademarks
Qt 4.3.2