Qt:Документация 4.3.2/threads
Материал из Wiki.crossplatform.ru
Root (Обсуждение | вклад) (поправил путь к картинке) |
Root (Обсуждение | вклад) |
||
(2 промежуточные версии не показаны) | |||
Строка 2: | Строка 2: | ||
=Поддержка потоков в Qt<br />= | =Поддержка потоков в Qt<br />= | ||
+ | |||
Qt предоставляет поддержку потоков в виде платформенно-независимых потоковых классов, потокобезопасного способа посылки сообщений и возможности установки соединений сигнал-слот через границы потоков. Это облегчает создание портируемых многопроцессорных приложений и использование преимуществ многопроцессорных машин. Мультипоточное программирование также полезная парадигма для выполнения занимающих продолжительное время действий без замораживания пользовательского интерфейса. | Qt предоставляет поддержку потоков в виде платформенно-независимых потоковых классов, потокобезопасного способа посылки сообщений и возможности установки соединений сигнал-слот через границы потоков. Это облегчает создание портируемых многопроцессорных приложений и использование преимуществ многопроцессорных машин. Мультипоточное программирование также полезная парадигма для выполнения занимающих продолжительное время действий без замораживания пользовательского интерфейса. | ||
Строка 8: | Строка 9: | ||
Данный документ предназначен для аудитории имеющей знания и опыт работы с многопоточными приложениями. Если вы плохо знакомы с потоками, см. наш [[#reading | Список рекомендованной литературы]]. | Данный документ предназначен для аудитории имеющей знания и опыт работы с многопоточными приложениями. Если вы плохо знакомы с потоками, см. наш [[#reading | Список рекомендованной литературы]]. | ||
+ | Темы: | ||
+ | |||
+ | *[[#the-threading-classes | Потоковые классы]] | ||
+ | **[[#creating-a-thread | Создание потока]] | ||
+ | **[[#synchronizing-threads | Синхронизация потоков]] | ||
+ | |||
+ | *[[#reentrancy-and-thread-safety | Монопоточность и потоковая безопасность]] | ||
+ | *[[#threads-and-qobjects | Потоки и объекты QObject]] | ||
+ | **[[#qobject-reentrancy | Монопоточные QObject]] | ||
+ | **[[#per-thread-event-loop | Цикл обработки сообщений потока]] | ||
+ | **[[#accessing-qobject-subclasses-from-other-threads | Вызов подклассов QObject из других потоков]] | ||
+ | **[[#signals-and-slots-across-threads | Соединение Сигналов и слотов между потоками]] | ||
+ | |||
+ | *[[#threads-and-implicit-sharing | Потоки и неявное совместное использование данных]] | ||
+ | *[[#threads-and-the-sql-module | Потоки и модуль SQL]] | ||
+ | *[[#recommended-reading | Рекомендуемая литература]] | ||
+ | <div id="the-threading-classes"></div> | ||
==Потоковые классы== | ==Потоковые классы== | ||
+ | |||
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]] предоставляет реализацию потока, который дремлет, пока не пробужен другим потоком. |
+ | <div id="creating-a-thread"></div> | ||
===Создание потока=== | ===Создание потока=== | ||
- | Для создания потока, определите подкласс [[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: | Строка 54: | ||
... | ... | ||
}</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]]). | ||
+ | <div id="synchronizing-threads"></div> | ||
===Синхронизация потоков=== | ===Синхронизация потоков=== | ||
- | |||
- | [[Qt:4.3.2/qmutex | QMutex]] | + | Классы [[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/ | + | [[Qt:Документация 4.3.2/qmutex | QMutex]] предоставляет взаимноисключающую блокировку или мьютекс. В одно и то-же время не больше одного потока может блокировать мьютекс. Если поток пытается заблокировать мьютекс в то время, как он уже заблокирован, то поток переходит в режим ожидания пока заблокировавший мютекс поток не освободит его (мьютекс). Мьютексы часто используются для защиты доступа к разделенным данным (т.е. данным, к которым можно обратиться от нескольких потоков одновременно). В разделе [[#reentrancy-and-thread-safety | Однопоточность и Потоковая Безопасность]] ниже, мы используем мьютексы для создания потокобезопасного класса. |
- | [[Qt:4.3.2/ | + | [[Qt:Документация 4.3.2/qreadwritelock | QReadWriteLock]] подобен [[Qt:Документация 4.3.2/qmutex | QMutex]], за исключением того, что делает различие между доступом к данным для "чтения" и "записи" и позволяет нескольким читателям одновременно обращаться к данным. По возможности используя [[Qt:Документация 4.3.2/qreadwritelock | QReadWriteLock]] вместо [[Qt:Документация 4.3.2/qmutex | QMutex]] можно сделать многопоточную программу более параллельными. |
- | [[Qt:4.3.2/ | + | [[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]]. | ||
+ | <div id="reentrant"></div><div id="thread-safe"></div><div id="reentrancy-and-thread-safety"></div> | ||
==Монопоточность и потоковая безопасность== | ==Монопоточность и потоковая безопасность== | ||
+ | |||
Везде в документации Qt, термины ''межпоточность'' и ''потоковая безопасность'' для определения того, как функции могут использоваться в многопоточных приложениях: | Везде в документации Qt, термины ''межпоточность'' и ''потоковая безопасность'' для определения того, как функции могут использоваться в многопоточных приложениях: | ||
*''Монопоточная'' функция может вызываться одновременно из нескольких потоков при условии, что каждый вызов функции обращается к разным экземплярам данных. | *''Монопоточная'' функция может вызываться одновременно из нескольких потоков при условии, что каждый вызов функции обращается к разным экземплярам данных. | ||
*''Потокобезопасные'' функции могут одновременно вызываться из разных потоков, даже если они обращаются к разделенным данным. Все обращения преобразуются в последовательную форму. | *''Потокобезопасные'' функции могут одновременно вызываться из разных потоков, даже если они обращаются к разделенным данным. Все обращения преобразуются в последовательную форму. | ||
+ | |||
Более широко, класс считается межпоточным если любая из его функций может быть вызвана одновременно из разных потоков для различных экземпляров класса, и потоконезависимой, если функции работают даже из различных потоков для одного экземпляра класса. | Более широко, класс считается межпоточным если любая из его функций может быть вызвана одновременно из разных потоков для различных экземпляров класса, и потоконезависимой, если функции работают даже из различных потоков для одного экземпляра класса. | ||
Строка 77: | Строка 101: | ||
#Увеличение или уменьшение значения регистра. | #Увеличение или уменьшение значения регистра. | ||
#Сохранение значения регистра обратно в основную память. | #Сохранение значения регистра обратно в основную память. | ||
+ | |||
Потоки 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: | Строка 118: | ||
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]](). | ||
+ | <div id="threads-and-qobjects"></div> | ||
==Потоки и объекты QObject== | ==Потоки и объекты QObject== | ||
- | |||
- | + | [[Qt:Документация 4.3.2/qthread | QThread]] наследует [[Qt:Документация 4.3.2/qobject | QObject]]. Он испускает сигналы, сообщающие о том, что поток начал или закончил работу, а также предоставляет несколько слотов. | |
+ | Очень интересно использование [[Qt:Документация 4.3.2/qobject | QObject]] в многопоточном приложении, он испускает сигналы, приходящие в слоты, находящиеся в других потоках, и посылает сообщения объектам "живущим" в других потоках. Это возможно потому, что каждый поток имеет собственный цикл обработки сообщений. | ||
+ | <div id="qobject-reentrancy"></div> | ||
===Монопоточность QObject=== | ===Монопоточность QObject=== | ||
- | |||
- | *''Дочерние по отношению к [[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]] монопоточен. Большинство из его не-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/qthread | QThread]].'' Это легко можно сделать создавая объекты в стеке в Вашей реализации [[Qt:4.3.2/qthread#run | run()]] | + | |
- | + | *''Дочерние по отношению к [[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/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]]. | ||
+ | <div id="per-thread-event-loop"></div> | ||
===Цикл обработки сообщений потока=== | ===Цикл обработки сообщений потока=== | ||
- | |||
- | + | Каждый поток может иметь собственный цикл обработки сообщений. Главный поток начинает цикл обработки сообщений используя [[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 | Соединение Сигналов и Слотов Между Потоками]] это описано подробнее. |
- | + | [[Image:threadsandobjects.png|center]] | |
- | + | Экземпляр [[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]], которое, в конце концов, будет обработано циклом обработки сообщений данного объекта. | |
- | + | Если никакой цикл обработки сообщений не запущен, то сообщения не будут доставлены объекту. Например, если вы создаете объект [[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#sendEvent | QCoreApplication::sendEvent]]() (в отличие от [[Qt:Документация 4.3.2/qcoreapplication#postEvent | postEvent()]]) может использоваться только для посылки сообщений живущим в том-же потоке, что и посылающая сообщения функции. | ||
+ | <div id="accessing-qobject-subclasses-from-other-threads"></div> | ||
===Вызов подклассов 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#run | QThread::run]](), потому, что испускание сигналов потокобезопасно. | ||
+ | <div id="signals-and-slots-across-threads"></div> | ||
===Связь сигналов и слотов между потоками=== | ===Связь сигналов и слотов между потоками=== | ||
+ | |||
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 | QObject::connect]]() устанавливает прямую связь, если отправитель и получатель принадлежат одному потоку, и связь с постановкой в очередь, если отправитель и получатель принадлежат различным потокам. Это можно изменить, передав дополнительный аргумент в [[Qt:Документация 4.3.2/qobject#connect | connect()]]. Помните, что использование прямых связей, когда отправитель и получатель проживают в разных потоках опасно в случае, если цикл обработки сообщений выполняется в потоке, где живет приемник, по той-же самой причине, по которой небезопасен вызов функций объекта, принадлежащего другому потоку. |
- | + | [[Qt:Документация 4.3.2/qobject#connect | QObject::connect]]() само по себе потокобезопасно. | |
- | + | В примере [[Qt:Документация 4.3.2/threads-mandelbrot | Mandelbrot]] используется соединение с постановкой в очередь для установки связи между рабочим и основным потоками. Для того, чтобы избежать заморозки цикла обработки сообщений основного потока (и, как следствие, заморозки пользовательского интерфейса приложения), все рекурсивные вычисления фрактала в Mandelbrot выполняются в отдельно потоке. Это поток испускает сигнал после вычислений, который рисует фрактал. | |
+ | Точно также, в примере [[Qt:Документация 4.3.2/network-blockingfortuneclient | Blocking Fortune Client]] используется отдельный поток для асинхронной связи с TCP-сервером. | ||
+ | <div id="threads-and-implicit-sharing"></div> | ||
==Потоки и неявное совместное использование данных== | ==Потоки и неявное совместное использование данных== | ||
- | 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. Атомарный подсчет ссылок очень быстр, намного быстрее, чем использование мьютекса. | ||
Строка 158: | Строка 191: | ||
Резюмируя скажем, что неявное совместное использование данных в Qt 4 действительно ''неявное'' разделение. Даже в многопоточных приложениях вы можете благополучно применять классы, использующие неявное разделение данных, так, будто это простые межпоточные классы. | Резюмируя скажем, что неявное совместное использование данных в Qt 4 действительно ''неявное'' разделение. Даже в многопоточных приложениях вы можете благополучно применять классы, использующие неявное разделение данных, так, будто это простые межпоточные классы. | ||
- | + | <div id="threads-and-the-sql-module"></div> | |
==Потоки и модуль SQL== | ==Потоки и модуль SQL== | ||
+ | |||
Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается. | Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается. | ||
Кроме того, библиотеки третьих лиц, используемые драйверами QSqlDriver могут наложить дополнительные ограничения на использование Модуля SQL в многопоточной программе. За дополнительной информацией обращайтесь к создателю клиента базы данных. | Кроме того, библиотеки третьих лиц, используемые драйверами QSqlDriver могут наложить дополнительные ограничения на использование Модуля SQL в многопоточной программе. За дополнительной информацией обращайтесь к создателю клиента базы данных. | ||
- | + | <div id="reading"></div><div id="recommended-reading"></div> | |
==Рекомендуемая литература== | ==Рекомендуемая литература== | ||
+ | |||
*[http://www.amazon.com/exec/obidos/ASIN/0134436989/trolltech/t Threads Primer: A Guide to Multithreaded Programming] | *[http://www.amazon.com/exec/obidos/ASIN/0134436989/trolltech/t Threads Primer: A Guide to Multithreaded Programming] | ||
*[http://www.amazon.com/exec/obidos/ASIN/0131900676/trolltech/t Thread Time: The Multithreaded Programming Guide] | *[http://www.amazon.com/exec/obidos/ASIN/0131900676/trolltech/t Thread Time: The Multithreaded Programming Guide] | ||
Строка 171: | Строка 206: | ||
{{Qt4.3.2_footer}} | {{Qt4.3.2_footer}} | ||
- |
Текущая версия на 10:45, 6 ноября 2008
Внимание: Актуальная версия перевода документации находится здесь |
__NOTOC__
Главная · Все классы · Основные классы · Классы по группам · Модули · Функции |
Содержание |
[править] Поддержка потоков в 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++ ++ и -- не всегда атомарны. В действительности они обычно расширяются до трех машинных инструкций:
- Загрузка значения переменной в регистр.
- Увеличение или уменьшение значения регистра.
- Сохранение значения регистра обратно в основную память.
Потоки A и B одновременно могут загрузить старое значение переменной, увеличить ее значение в регистре и сохранить значение переменной в памяти, но переменная будет увеличена только однажны!
Становится ясно, что обращения должны быть упорядочены: Поток A должен выполнить шаги 1, 2, 3 без прерывания (автоматического) прежде, чем поток B сможет выполнить теже шаги; или наоборот. Самый легкий способ создания потокобезопасного класса состоит в том, чтобы защитить весь доступ к членам с помощью QMutex:
class Counter { public: Counter() { n = 0; } void increment() { QMutexLocker locker(&mutex); ++n; } void decrement() { QMutexLocker locker(&mutex); --n; } int value() const { QMutexLocker locker(&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). Это также доет возможность соединить сигналы из любых потоков со слотами в определенном потоке. В разделе Соединение Сигналов и Слотов Между Потоками это описано подробнее.
Экземпляр 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 в многопоточной программе. За дополнительной информацией обращайтесь к создателю клиента базы данных.
[править] Рекомендуемая литература
- Threads Primer: A Guide to Multithreaded Programming
- Thread Time: The Multithreaded Programming Guide
- Pthreads Programming: A POSIX Standard for Better Multiprocessing
- Win32 Multithreaded Programming
Copyright © 2007 Trolltech | Trademarks | Qt 4.3.2
|