8-900-374-94-44
[email protected]
Slide Image
Меню

Потоки qt – Qt/C++ — Урок 047. QThread

QThread – потоки в Qt

Оглавление

  • Процессы и потоки в Qt
  • QProcess – процессы в Qt
  • QThread – потоки в Qt
  • Приоритеты потоков в Qt
  • Обмен сообщениями между потоками
  • Сигнально-слотовые соединения
  • Связь между потоками с помощью высылки событий
  • Синхронизация
  • QMutex — мьютексы в Qt
  • QWaitCondition — условные ожидания в Qt
  • Взаимные блокировки
  • QSemaphore — семафоры в Qt

Потоки становятся все более популярными. Для реализации потоков Qt предоставляет класс QThread. Но давайте сначала разберемся, что же собой представляют потоки.

Поток это независимая задача, которая выполняется внутри процесса и разделяет вместе с ним общее адресное пространство, код и глобальные данные.

Процесс, сам по себе, не является исполнительной частью программы, поэтому для исполнения программного кода он должен иметь хотя бы один поток (далее основной поток). Конечно, можно создавать и более одного потока. Вновь созданные потоки начинают выполняться сразу же, параллельно с главным потоком, при этом их количество может изменяться одни создаются, другие завершаются. Завершение основного потока приводит к завершению процесса, независимо от того, существуют другие потоки или нет.Создание нескольких потоков в процессе получило название многопоточность.

Многопоточность требуется для выполнения действий в фоновом режиме, параллельно с действиями основной программы, и позволяет разбить выполнение задач на параллельные потоки, которые могут быть абсолютно независимы друг от друга. А если приложение выполняется на компьютере с несколькими процессорами, то разделение на потоки может значительно ускорить работу всей программы, так как каждый из процессоров получит отдельный поток для выполнения. К тому же, в последнее время используется все больше компьютеров, оснащенных двухъядерными процессорами (dual core), что делает многопоточное программирование еще более популярным.

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

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

Qt.

Так с чего же все-таки начинается многопоточное программирование? Оно начинается с наследования класса QThread и переопределении в нем чисто виртуального метода run(), в котором должен быть реализован код, который будет исполняться в потоке. Например:

Второй шаг заключается в создании объекта класса потока и вызове метода start(), который вызовет, в свою очередь, реализованный нами метод run().

Источник:http://qt-doc.ru/qthread-potoki-v-qt.html

img59.ru

Процессы и потоки в Qt

  • 20.11.2011 Автор: admin

    Процессы представляют собой программы, независимые друг от друга и загруженные для исполнения. Каждый процесс должен создавать хотя бы один поток, называемый основным. Основной поток процесса создается в момент запуска программы. Однако сам процесс может создавать несколько потоков одновременно. Далее…

  • 20.11.2011 Автор: admin

    В том случае, когда пользователь или программа производят запуск другой программы, операционная система всегда создает новый процесс. Процесс — это экземпляр программы, загруженной в память компьютера для выполнения. Далее…

  • 20.11.2011 Автор: admin

    Потоки становятся все более популярными. Для реализации потоков Qt предоставляет класс QThread. Но давайте сначала разберемся, что же собой представляют потоки. Далее…

  • 20.11.2011 Автор: admin

    У каждого потока есть приоритет, указывающий процессору, как должно протекать выполнение потока по отношению к другим потокам. Приоритеты разделяются по группам: Далее…

  • 20.11.2011 Автор: admin

    Один из важнейших вопросов при многопоточном программировании — это обмен сообщениями. Действительно, если вы, например, в одном потоке создаете растровое изображение и хотели бы переслать его объекту другого потока, то каким образом вы можете это сделать? Далее…

  • 20.11.2011 Автор: admin

    Итак, мы можем взять сигнал объекта одного потока и соединить его со слотом объекта другого потока. Как мы уже знаем, соединение с помощью метода connect() предоставляет дополнительный параметр, обозначающий режим обработки и равный, по умолчанию, значению Qt::AutoConnection, которое соответствует автоматическому режиму. Как только происходит высылка сигнала, Qt проверяет — происходит связь в одном и том же или разных потоках. Если это один и тот же поток, то высылка сигнала приведет к прямому вызову метода. В том случае, если это разные потоки, сигнал будет преобразован в событие и доставлен нужному объекту. Далее…

  • 20.11.2011 Автор: admin

    Высылка событий — это еще одна из возможностей для осуществления связи между объектами. Как мы знаем, есть два метода для высылки событий: QCoreApplication::postEvent() и QCoreApplication::sendEvent(). Здесь есть небольшой нюанс, который нужно знать: высылка событий методом postEvent() обладает надежностью в потоках, а при помощи метода sendEvent() — нет. Поэтому при работе с разными потоками всегда используйте метод postEvent(). На рисунке показано, как с помощью механизма обмена событиями разных потоков можно осуществлять связь между двумя потоками. Поток может высылать события другому потоку, который, в свою очередь, может ответить другим событием и т. д. Сами же события, обрабатываемые циклами событий потоков, будут принадлежать тем потокам, в которых они были созданы. Далее…

  • 20.11.2011 Автор: admin

    Основные сложности возникают тогда, когда потокам нужно совместно использовать одни и те же данные. Так как несколько потоков могут одновременно обращаться и записывать данные в одну область, то это может привести к нежелательным последствиям. Представьте себе такую ситуацию: один поток занимается вычислениями, используя значения какой-нибудь глобальной переменной, а в это время другой поток вдруг изменяет значение этой переменной, но поток, занимающийся вычислениями, продолжает свою работу, ничего не подозревая и используя уже измененное значение. Для предотвращения подобных ситуаций требуется механизм, позволяющий блокировать данные, когда один из потоков намеревается их изменить. Этот механизм получил название синхронизация. Далее…

  • 20.11.2011 Автор: admin

    Мьютексы (mutex) обеспечивают взаимоисключающий доступ к ресурсам, гарантирующий то, что критическая секция будет обрабатываться только одним потоком. Поток, владеющий мьютексом, обладает эксклюзивным правом на использование ресурса, защищенного мьютексом, и другой поток не может завладеть уже занятым мьютексом. Далее…

  • 20.11.2011 Автор: admin

    Библиотека Qt предоставляет класс QWaitCondition, обеспечивающий возможность координации потоков. Если поток намеревается дождаться разблокировки ресурса, то он вызывает метод QWaitCondition::wait() и, тем самым, входит в режим ожидания. Выводится он из этого режима в том случае, если поток, который заблокировал ресурс, вызовет метод QWaitCondition::wakeOne() или QWaitCondition::wakeAll(). Разница этих двух методов в том, что первый выводит из состояния ожидания только один поток, а второй — все сразу. Также для потока можно установить время, в течение которого он может ожидать разблокировки данных. Для этого нужно передать в метод wait() целочисленное значение, обозначающее временной интервал в миллисекундах. Далее…

  • 20.11.2011 Автор: admin

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

  • 20.11.2011 Автор: admin

    Семафоры являются обобщением мьютексов. Как и мьютексы, они служат для защиты критических секций, чтобы доступ к ним одновременно могло иметь определенное число потоков. Все другие потоки обязаны ждать. Предположим, что программа поддерживает пять ресурсов одного и того же типа, одновременный доступ к которым может быть предоставлен только пяти потокам. Как только все пять ресурсов будут заблокированы, следующий поток, запрашивающий ресурс данного типа, будет приостановлен до освобождения одного из них. Принцип действия семафоров очень прост. Они начинают действовать с установленного значения счетчика. Каждый раз, когда поток получает право на владение ресурсом, значение этого счетчика уменьшается на единицу. И наоборот, когда поток уступает право владения этим ресурсом, счетчик увеличивается на единицу. При значении счетчика равном нулю семафор становится недоступным. Механизм семафоров реализует класс QSemaphore. Счетчик устанавливается в конструкторе при создании объекта этого класса. Далее…

  • qt-doc.ru

    Потоки в Qt: Параллельное программирование

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

    Тестовая площадка: Фрактал Мандельброта

    Чтобы можно было проводить какие-либо сравнения, различные варианты использования потоков мы будем рассматривать на примере решения одной и той же задачи. В качестве такой задачи возьмем построение фрактала Мандельброта.

    Сначала определим виджет, а также несколько вспомогательных классов и структур, которые позволят нам легко добавлять новые версии решений:

    Структура FractalConf определяет конфигурацию фрактала, который мы хотим нарисовать. Она содержит поля zxMin , zxMax , zyMin , zyMax , step и maxIterCount . Разберемся с их назначением с помощью следующего графика:

    На графике мы видим две координатные сетки: внешнюю с координатами (x; y) и внутреннюю с координатами (zx, zy) . Обе они определяют одно и то же изображение фрактала. Однако внешняя соответствует его графическому представлению в пикселях, которое предназначено для вывода на экран с началом координат в левом верхнем углу. Внутренняя же определяет математическое представление фрактала Мандельброта с более традиционным расположением начала координат. Обратим внимание, что на осях zx и zy отложено по две точки: zxMin=-2.0 , zxMax=1.0 и zyMin=-1.2 , zyMax=1.2 , которые задают границы прямоугольной области. Эти значения и определены в одноименных полях структуры FractalConf .

    Кроме того, нам необходимо выбрать шаг, с которым мы будем двигаться по внутренней координатной сетке фрактала. Это связано с тем, что рисовать фрактал мы будем по точкам (пикселям). И расстояние от предыдущей до следующей точки должно быть постоянным. Такое расстояние определено в поле структуры FractalConf и называется step .

    Для каждой точки фрактала Мандельброта с координатами (zx; zy) определяется некая глубина, для расчета которой используется рекуррентный алгоритм. Чтобы этот алгоритм не оказался бесконечным, необходимо определить максимальную глубину, то есть максимальное количество итераций, после выполнения которых мы считаем, что глубина найдена. Это значение также определено в структуре FractalConf в поле с maxIterCount .

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

    Дальше поговорим о классе виджета FractalView . В конструкторе мы заранее передаем ему конфигурацию фрактала FractalConf . То есть мы будем рисовать один и тот же фрактал Мандельброта разными методами, которые представим с помощью абстрактного класса FractalDrawAlgorithm . Конкретные алгоритмы, реализующие этот класс, можно будет добавлять к экземплярам класса FractalView с помощью функции-члена addAlogrithm() . Получаем некую вариацию паттерна Стратегия.

    Связь между виджетом и алгоритмами определяется с помощью сигналов и слотов. Для запуска алгоритма используется виртуальный слот FractalDrawAlgorithm::start() , который будет вызываться в приложении по нажатию кнопки на виджете. О результатах своей работы алгоритмы будут сообщать с помощью сигналов partAvailable() и ready() . Первый будет привязан к слоту виджета onPartAvailable() и вернет промежуточный результат построения фрактала в виде координат (x; y) и изображения QImage . Такое решение связано с тем, что мы будем рисовать фрактал в нескольких потоках по частям. Поэтому для наглядности имеет смысл отображать не весь фрактал сразу, а компоновать его из полученных кусочков, как мозаику. Это повышает отзывчивость интерфейса приложения и пользователям кажется, что оно работает быстрее. Когда же алгоритм завершит свою работу, то он должен вызвать сигнал ready() , чтобы сообщить виджету, что все готово. Нам понадобится это для того, чтобы делать замеры времени и сравнить скорость работы различных версий алгоритмов.

    Рассмотрим реализацию класса FractalView :

    Как видим, здесь нет ничего необычного. В конструкторе мы просто компонуем элементы UI на нашем виджете. Кроме того, заметьте, что еще в конструкторе мы проинициализировали изображение m_img с шириной и высотой, которую будет иметь фрактал с переданной конфигурацией. На виджете у нас будет всего одна кнопка, комбо-бокс и несколько лейблов. Кнопка будет запускать алгоритм рисования фрактала в слоте виджета onStarted() . Причем в этом слоте мы запускаем таймер, с помощью которого определим время, затраченное на работу алгоритма. Выбор алгоритма осуществляется с помощью выпадающего списка.

    В функции-члене addAlgorithm() сигналы добавленных алгоритмов привязываются к слотам onPartAvalilabe() и onFinished() . Об их назначении мы уже говорили. Однако заметим, что склеивание частей фрактала осуществляется с помощью QPainter и его функции-члена drawImage() . Затем получившееся изображение выводится вызовом setPixmap() . А после поступления сигнала готовности в слот onFinished() мы просто выводим прошедшее с начала запуска алгоритма время.

    В результате у нас получился такой виджет:

    Пришло время добавить алгоритмы.

    Однопоточный алгоритм построения фрактала

    Начнем с однопоточного алгоритма построения фрактала. Без него нам все равно не обойтись. Возьмем простейшую реализацию функции, которая нарисует нам фрактал Мандельброта с заданной конфигурацией:

    Функция makeIter() находит глубину точки фрактала, о которой мы говорили в предыдущем разделе. Ее реализация стандартна, поэтому останавливаться на ней не будем. Нашей рабочей функцией является drawFractal() . Она превращает конфигурацию фрактала FractalConf в соответствующее изображение QImage , которое мы можем отобразить на экране. Сначала мы просто создаем пустое изображение QImage , на котором будем рисовать. Его ширина и высота в пикселях определяются с помощью подготовленных ранее функций getImageWidth() и getImageHeight() .

    Построение фрактала мы проводим в двойном цикле по пиксельной координатной сетке изображения (x; y) с началом координат в левом верхнем углу. Параллельно с x и y мы двигаемся по математическим координатам zx и zy в заданной конфигурацией фрактала области. Для координат изображения в пикселях все совсем просто. У нас есть известное количество пикселей с двумя координатами и каждый из них должен быть заполнен. Ровно это мы и делаем, проходя в цикле по всему изображению. Но по точкам в математической координатной сетке фрактала (zx, zy) так легко ходить мы не можем. И здесь на помощь приходит шаг step . Таким образом, мы начинаем с точек (x; y) = (0; 0) и (zx; zy) = (zxMin; zyMin) . Внутренний цикл проходит по оси y , поэтому далее последуют точки (x; y) = (0; 1) и (zx; zy) = (zxMin; zyMin + step) . Затем мы попадем в точки (x; y) = (0; 2) и (zx; zy) = (zxMin; zyMin + 2 * step) . И так до тех пор, пока не пройдем каждый пиксель изображения, а заодно и соответствующие точки фрактала в нужной нам области. Здесь стоит обратить внимание, что направление роста осей y и zy будут отличаться, поскольку они направлены в разные стороны, но нас это не сильно волнует, так как фрактал вдоль этих осей симметричен.

    Как и для склейки частей фрактала, в drawFractal() используется QPainter , однако мы рисуем по точкам, поэтому используется функция-член drawPoint() . Для каждой точки мы сначала вычисляем глубину с помощью makeIter() , а затем полученное значение превращаем в цвет. Если глубина достигла установленного максимума, то делаем точку черной, иначе определяем цвет на основе получившейся глубины с помощью выражения count 11 . Вообще, существует множество способов раскраски фрактала Мандельброта, но я для примера выбрал один из простейших.

    Теперь реализуем первый алгоритм рисования, который назовем EasyFractalDrawAlgorithm . Он и правда простой:

    Этот алгоритм основан на приведенной ранее функции drawFractal() . Отдельных частей мы здесь не формируем, а возвращаем сразу весь фрактал. А затем сразу же сообщаем о том, что все готово.

    Напишем функцию main() , и проведем первое испытание:

    Вот результат запуска на моем компьютере:

    Построение заняло 621 миллисекунду. Не так уж и плохо, но это все же заметная глазу пауза. К тому же, приложение на это время стало неотзывчивым, поскольку главный поток был занят вычислениями и не мог реагировать на действия пользователя.

    Пришло время потоков приступить к выполнению своих обязанностей.

    Многопоточный алгоритм построения фрактала на основе QThread

    Прежде чем перейти к реализации многопоточной версии алгоритма, определим вспомогательный класс FractalDrawTask :

    Он наследует классы QObject и QRunnable . Первый нужен для того, чтобы мы могли пользоваться сигналами и слотами, а причины использования второго станут понятны немного позже. Конструктор класса FractalDrawTask принимает координаты x и y , а также конфигурацию фрактала FractalConf . Координаты в самом классе напрямую не используются, однако он возвращает их с помощью сигнала ready() в слоте run() вместе с изображением фрактала, построенным по заданной конфигурации.

    Теперь посмотрим на первую многопоточную версию алгоритма:

    Количество потоков, которое будет задействовано алгоритмом, мы можем указать через конструктор класса в параметре threadCount . Использоваться этот параметр будет в реализации слота start() . Рассмотрим ее поподробнее. Ключевым моментом здесь является то, что мы делим ожидаемую высоту готового изображения фрактала в пикселях на количество потоков. Например, для четырех потоков имеем:

    Расстояние между началом и концом каждой получившейся горизонтальной полосы постоянно и равно высоте изображения в пикселях, разделенной на количество потоков. Зная высоту одной полосы в пикселях, которую обозначили BLOCK_HEIGHT , легко найти соответствующую высоту полосы во внутренней системе координат (zx; zy) . Для этого просто умножим BLOCK_HEIGHT на величину шага step , определенную во FractalConf . Далее следует простой цикл, в котором мы двигаемся по оси y с шагом, равным BLOCK_HEIGHT . Параллельно мы двигаемся по оси zy с найденным ранее шагом BLOCK_STEP .

    Для каждой полосы мы определяем соответствующую конфигурацию FractalConf и передаем ее вместе с координатами x и y в слот start() . В этом слоте происходит непосредственное создание и вызов потока для обработки переданной полосы фрактала. Сначала мы создаем новый объект задачи FractalDrawTask , передавая ей переданные параметры. Далее определяем новый поток, в котором будет решаться эта задача. Но перед запуском потока связываем сигналы и слоты:

    Первые два коннекта определяют связь между завершением потока и удалением объектов, чтобы предотвратить утечки памяти. Затем мы привязываем сигнал ready() к слоту onPartReady() , в котором мы перенаправим результаты в виджет, чтобы он смог отобразить подготовленную часть фрактала. Кроме того, в слоте onPartReady() мы также проверим, была ли эта часть последней или есть еще. Если окажется, что частей больше нет, то мы вызовем сигнал ready() . Завершение работы потока также привязывается к сигналу ready() объекта task . А запуск слота run() для решения задачи связывается с сигналом потока started() .

    Важно не забыть перевести объект task для работы в потоке и все запустить:

    Для добавления этого алгоритма в наш виджет достаточно добавить в функции main() строку следующего вида:

    Мой процессор имеет 8 ядер, поэтому такой вариант показал максимальную производительность и составил в среднем 180 миллисекунд. Для однопоточной реализации была 621 миллисекунда, то есть скорость возросла примерно в три с половиной раза. Я пробовал запускать обе версии со значениями параметра maxIterCount побольше и разница оказалась примерно такой же. Однако увеличение скорости в 3,5 раза уже серьезный показатель. Более того, многопоточная версия не блокирует графический интерфейс пользователя и практически сразу начинает выдавать фрагменты изображения с фракталом, а это лишний раз показывает, что приложение не зависло, а делает свою работу.

    Многопоточный алгоритм построения фрактала на основе QThreadPool

    Оказывается, рассмотренный выше пример можно реализовать еще проще. Для этого в Qt существует QThreadPool . Рассмотрим соответствующую версию алгоритма:

    Новый алгоритм ThreadPoolFractalDrawAlgorithm наследует класс MultiThreadedFractalDrawAlgorithm , чтобы повторно использовать цикл прохода по полосам фрактала и всего лишь реализует свою версию слота start() . А в ней все значительно лаконичнее, чем было у его предка. Мы также создаем объект задачи task , но теперь достаточно лишь привязать сигнал ready() с нашим слотом-обработчиком onPartReady() , а обо всем остальном уже позаботится QThreadPool . Он и поток создаст, и задачу запустит и память освободит. А что еще нужно? — Чтобы быстро работало. Но и здесь все прекрасно. В среднем производительность ничем не отличается от версии, где мы сами создавали потоки, поэтому для реализации простой логики взаимодействия потоков, как у нас, этот подход является наилучшим.

    Но не так быстро. А откуда QThreadPool знает, каким образом нужно запускать наш task ? — Все очень просто. Помните, при объявлении класса FractalDrawTask мы наследовали его от QObject и QRunnable ? Так вот второй унаследованный класс здесь нам и пригодился. Он имеет виртуальную функцию-член run() , которую мы определили в виде слота и реализовали. А QThreadPool при вызове start() как раз и ожидает указатель на QRunnable , для которого потом запускает run() в потоке.

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

    Многопоточный алгоритм построения фрактала на основе QtConcurrent

    Я сначала сомневался, стоит ли включать сюда этот вариант, но решил, что для полноты картины не помешает упомянуть и его. Однако заранее отмечу, что такой вариант использования, который мы сейчас рассмотрим, является допустимым, но не целевым. Думаю, что по многопоточности будут еще заметки и в них мы уже рассмотрим более подходящий пример. А для решения нашей текущей задачи по построению фрактала лучше использовать QThreadPool . Но перейдем к делу. Вот реализация на основе QtConcurrent::run() :

    Получившаяся реализация чем-то похожа на то, что было в версии на основе QThreadPool . Однако здесь мы вынуждены явно установить соединение сигнала ready() и слота deleteLater() для удаления объекта. Кроме того, нам приходится передавать указатель на функцию run() , которую мы хотим вызвать. Скорость работы этой версии, как и ожидалось, ничем не хуже и не лучше других рассмотренных многопоточных решений. Если выбирать между этой реализацией и вариантом на основе QThread , то, конечно, более предпочтительным выглядит QtConcurrent . Однако повторю еще раз, что сила QtConcurrent заключается в решении другого типа задач, но об этом мы поговорим уже в другой заметке (см. Пример использования QtConcurrent).

    Заключение

    В этой заметке мы коротко прошлись по базовым принципам работы с потоками в Qt-приложениях. Рассмотрели несколько вариантов алгоритма построения фрактала Мандельброта с применением QThread , QThreadPool и QtConcurrent . Убедились, что прирост производительности этих решений по сравнению с однопоточной версией и правда есть, причем не маленький. И остановились на том, что для рассмотренной задачи наиболее оптимальным выбором является использование QThreadPool , который сочетает в себе простоту использования и гибкость работы.

    Не забудь поделиться ей с друзьями!

    Источник:http://itnotesblog.ru/note.php?id=145

    img59.ru

    Многопоточный сервер Qt | Журнал-И

    В предыдущей статье [1] была рассмотрена работа с сокетами в библиотеке Qt. Наш сервер сетевого чата работал в одном потоке. Задача текущей статьи — описание многопоточного сервера.

    Однако, если сервер просто принимает сообщение и передает его всем подключенным клиентам — распараллеливать нечего и потоки не особо нужны. В связи с этим, мы немного усложним задачу — наш сервер будет вычислять передаваемые арифметические выражения (для вычисления выражений используем Qt Script [2]).

    Серверу (а точнее, сокету) надо добавить новый функционал, и в этом нам поможет шаблон проектирования «Декоратор». Когда сервер начнет выполнять все необходимые нам функции, приступим к распараллеливанию. В статье рассмотрены 2 варианта:

    • создание отдельного потока на каждое подключение;
    • использование стандартного пула потоков библиотеки Qt.

    В предыдущих статьях уже был описан шаблон параллельного программирования «поставщик-потребитель» [3]. Пул потоков является родственным паттерном, но мы не будем писать свою реализацию, а используем готовую.

    Шаблон проектирования Decorator

    Декоратор — структурный шаблон проектирования, который является гибкой альтернативой наследованию. Чаще всего декоратор применяется в случаях, когда объекту требуется добавить функциональность во время выполнения программы, но может применяться в других случаях.

    рис. 1 Диаграмма классов шаблона Декоратор

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

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

    • добавлять новую функциональность уже существующим объектам прямо во время выполнения. Гамма описывает пример с полосами прокрутки, которые могут появляться лишь при определенных условиях[4];
    • смешивать функциональность. Например, графический элемент может одновременно изменять цвет, иметь привязку к сетке и предоставлять полосу прокрутки — если бы мы решали эту (повседневную и простую, в общем-то) задачу лишь наследованием — иерархия классов получилась бы громадной («комбинаторный взрыв»).

    Как показано на рисунке 1, и компонент, и декоратор реализуют один и тот же интерфейс, т.е. декоратор является таким же компонентом, как и все остальные. Тем не менее, Гамма указывает, что в некоторых случаях, декоратор может сильно отличаться от других компонентов и это надо учитываь [4]. Класс Decorator хранит ссылку на декорируемый компонент, а его наследники каким-либо образом переадресуют этому компоненту запросы, но кроме этого, выполняют еще какие-либо действия (рисуют рамку или выводят ползунки прокрутки, например).

    Шаблон проектирования «Декоратор». Пример

    В нашей задаче многопоточного сервера, декорироваться будет сокет. За основу взят код однопоточного сервера чата [1].

    рис. 2 до имплементации шаблона Decorator

    На рисунке 2 видно, что сервер работает с указателем на ISocketAdapter, по которому находится экземпляр ServerSocketAdapter, который умеет принимать и отправлять текстовые сообщения. Нам требуется научить сервер новым трюкам, при этом, мы не хотим сильно изменять уже написанный и отлаженный код. Очевидно, надо породить нового наследника ISocketAdapter, который будет делать тоже, что и ServerSocketAdapter и еще чуть-чуть.

    рис. 3 decorator pattern implementation

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

    Видно, что базовый класс декоратора остается абстрактным, наследует интерфейс сокета и, одновременно, агрегирует сокет и соединяет свои слоты с его сигналами.

    CalcSockDecorator по-особенному обрабатывает поступающие сообщения (слот on_message), все остальные к запросы к себе он транслирует агрегируемому объекту. При поступлении сообщения, он пытается выполнить его как выражение ECMAScript, в случае возникновения исключительной ситуации возвращает строку с ошибкой, иначе — результат вычисления. Именно слот on_message выполняет самую сложную работу сервере, которую мы будем пытаться выполнять в отдельном потоке в остальной части статьи.

    Коренным образом изменилось только создания сокета — создается экземпляр CalcSockDecorator вместо ServerSocketAdapter. Кроме того, на листинг 3 виден подводный камень — с использованием механизмов библиотеки Qt нам придется следить за тем, чтобы клиентский код был связан с сигналами и слотами декораторов самого верхнего уровня. Иными словами, если пользователь решит передавать сообщения зашифрованными — сервер должен будет:

    1. создать соответствующий декоратор сокета, передав старый сокет в качестве аргумента;
    2. отсоединить весь клиентский код от старого сокета;
    3. соединить клиентский код с декорированным сокетом.

    Все это может быть очень трудно реализовываться и, кажется, возможны случаи, в которых из за сигналов и слотов использовать паттерн декоратор будет почти не возможно. Это надо учитывать при проектировании.

    Исходный код примера сервера с декорированным сокетом можно скачать.

    Многопоточный сервер. Поток на каждого клиента

    Очевидно, если мы хотим работать с каждым клиентом в отдельном потоке, то поток должен создаваться в момент подключения. Изменения коснутся лишь метода сервера on_newConnection.

    В строках 7-9 описано создание потока и перемещение в него объекта-обработчика сокета. Ничего нового в этом нет и, очевидно, это не лучшее решение. Поток Qt — это поток операционной системы, который, в свою очередь, является достаточно крупным объектом. Если к нашему чату подключится тысяча пользователей, серверу будет очень не легко, даже если пользователи почти не будут общаться. В связи с этим, количество потоков на сервере стараются ограничить.

    Можно, например, завести на сервере определенное количество потоков (пул потоков) и направлять новых клиентов к наименее загруженным потокам. Примерно такой вариант мы уже использовали при написании парсера проектов с биржи фриланса [3], но в библиотеке Qt есть готовый пул потоков, который используется чуть-чуть иначе (ниже описано подробнее).

    Важно отметить, что в некоторых языках (Erlang, например) поддерживаются легковесные потоки. Для этих языков решение с отдельным потоком на каждого клиента было бы не только самым простым, но правильным и естественным.

    Исходный код многопоточного сервера тоже можно скачать.

    Пул потоков Qt

    Пул потоков Qt (QThreadPool) управляет набором потоков (QThread). Количество потоков задается методом maxThreadCount(), по умолчанию их ровно столько, сколько ядер имеется у вашего процессора. Когда мы писали свой пул, потоки там существовали вечно, но в QThreadPool поток удаляется если в течении определенного времени в него не поступают задачи, задать такой таймаут можно методом setExpiryTimeout(). При поступлении задачи поток вновь будет создан.

    Мы могли бы создать на сервере экземпляр QThreadPool и добавлять задачи в него, однако, каждая программа, написанная с использованием Qt уже имеет запущенный пул потоков (глобальный пул программы). Обратиться к глобальному пулу можно с помощью статической функции QThreadPool::globalInstance(). С этим пулом мы и будем работать.

    Задача, добавляемая в пул должна наследовать класс QRunnable — это абстрактный класс с чисто виртуальным методом run(). Метод run() должен содержать код, который мы хотим вынести в отдельный поток. После завершения работы, объект может быть автоматически удален, если установлен соответствующий флажок (по умолчанию установлен). Установить или снять такой флажок можно методом QRunnable::setAutoDelete().

    Чтобы передать результаты выполнения задачи во внешний мир можно использовать механизм сигналов и слотов, но QRunnable не является наследником QObject, поэтому наша задача для пула будет использовать множественное наследование. Передавать задаче данные, которые должны быть обработаны, удобно во время конструирования объекта.

    В QThreadPool мы будем добавлять задачи, связанные с вычислением выражения, полученного через сокет.

    Теперь при получении сообщения, объект CalcSockDecorator создает объект Calc и добавляет его в пул вызовом QThreadPool::start(), после чего, ожидает сигнала с результатами от этого объекта.

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

    Скачать исходный код такого замечательного сервера тоже можно.

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

    Источник:http://pro-prof.com/archives/1390

    img59.ru

    Принципы многопоточности qt 5 | Журнал-И

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

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

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

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

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

    • QThread предоставляет средства для создания нового потока.
    • QThreadStorage обеспечивает хранение данных потока.
    • QThreadPool управляет пулом (pool) потоков, которые запускают объекты QRunnable.
    • QRunnable — абстрактный класс, представляющий запускаемый (runnable) объект.
    • QMutex предоставляет взаимоисключающую блокировку или мьютекс.
    • QMutexLocker — вспомогательный класс, предоставляющий удобное блокирование и разблокирование мьютексов QMutex.
    • QReadWriteLock обеспечивает блокировку, разрешающую совместный доступ для чтения.
    • Вспомогательные классы QReadLocker и QWriteLocker автоматически блокируют и разблокируют QReadWriteLock.
    • QSemaphore предоставляет целочисленный семафор (обобщенный мьютекс).
    • QWaitCondition предоставляет реализацию потока, который засыпает, пока не будет пробужден другим потоком.
    • QAtomicInt предоставляет атомарные операции с целыми числами.
    • QAtomicPointer предоставляет атомарные операции с указателями.

    Замечание: Классы работы с потоками Qt реализуются с помощью «родных» средств API; например, Win32 и pthreads. Потому они могут взаимодействовать с «родными» потоками этого API.

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

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

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

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

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

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

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

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

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

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

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

    Обратите внимание на то, что классы синхронизации Qt зависят от использования правильно выровненных (properly aligned) указателей. Например, вы не можете использовать упакованные классы вместе с MSVC.

    QtConcurrent

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

    QtConcurrent включает в себя несколько API функционального стиля программирования для параллельной обработки списков, включая реализации MapReduce и FilterReduce для систем с разделяемой памятью (shared-memory) (не распределенных), и классов для управления асинхронными вычислениями в приложениях с ГПИ:

    • QtConcurrent::map() применяет функцию к каждому элементу в контейнере, изменяя элементы на месте.
    • QtConcurrent::mapped() похожа на map() за исключением того, что она возвращает новый контейнер с изменениями.
    • QtConcurrent::mappedReduced() похожа на mapped() за исключением того, что модифицированные результаты преобразуются (reduced) или свёртываются (folded) в единственный результат.
    • QtConcurrent::filter() удаляет все элементы из контейнера, основываясь на результате фильтр-функции.
    • QtConcurrent::filtered() похожа на filter() за исключением, того что он возвращает новый контейнер с отфильтрованными результатами.
    • QtConcurrent::filteredReduced() похожа на filtered() за исключением того, что отфильтрованные результаты уменьшены или свёрнуты в единственный результат.
    • QtConcurrent::run() запускает функцию в другом потоке.
    • QFuture представляет результат асинхронных вычислений.
    • QFutureIterator делает возможным процесс итерации по результатам, доступным через QFuture.
    • QFutureWatcher делает возможным слежение за QFuture используя сигналы и слоты.
    • QFutureSynchronizer — вспомогательный класс, который автоматически синхронизирует несколько объектов QFuture.

    Qt Concurrent поддерживает несколько STL-совместимых контейнерных и итераторных типов, но лучше работает с контейнерами Qt, которые имеют итераторы случайного доступа (random-access), например, QList или QVector. Функции map и filter работают как с контейнерами, так и с начальными/конечными итераторами.

    Источник:http://doc.crossplatform.ru/qt/4.5.0/threads.html

    img59.ru

    используйте потоки Qt или потоки Python? Ru Python

    Я пишу приложение GUI, которое регулярно извлекает данные через веб-соединение. Поскольку это извлечение занимает некоторое время, это приводит к тому, что пользовательский интерфейс не реагирует во время процесса поиска (его нельзя разделить на более мелкие части). Вот почему я хотел бы перенаправить веб-соединение на отдельный рабочий поток.

    [Да, я знаю, теперь у меня две проблемы .]

    В любом случае, приложение использует PyQt4, поэтому я хотел бы знать, какой лучший выбор: использовать потоки Qt или использовать модуль threading Python? Каковы преимущества / недостатки каждого? Или у вас есть совершенно другое предложение?

    Изменить (re bounty): Хотя решение в моем конкретном случае, вероятно, будет использовать неблокирующий сетевой запрос, например, предложенный Джеффом Обром и Лукашем Лалинским (так что в основном проблемы с параллелизмом для сетевой реализации) мне все же хотелось бы больше Углубленный ответ на общий вопрос:

    Каковы преимущества и недостатки использования потоков PyQt4 (то есть Qt) поверх собственных потоков Python (из модуля threading передачи)?


    Редактировать 2: Спасибо всем за ответы. Хотя нет 100% -ного соглашения, похоже, широко распространено мнение, что ответ «используется Qt», поскольку преимущество этого заключается в интеграции с остальной библиотекой, не создавая реальных недостатков.

    Для тех, кто хочет выбрать между двумя реализациями потоковой передачи, я настоятельно рекомендую вам прочитать все предоставленные здесь ответы, включая поток списков рассылки PyQt, на который ссылаются abbot.

    Было несколько ответов, которые я рассмотрел за щедрость; в конце концов я выбрал аббата для очень релевантной внешней ссылки; это был, однако, близкий звонок.

    Еще раз спасибо.

    Это было обсуждено не так давно в списке рассылки PyQt. Цитируя комментарии Джованни Баджо на эту тему:

    Это в основном то же самое. Основное отличие состоит в том, что QThreads лучше интегрированы с Qt (асинхронные сигналы / слоты, цикл событий и т. Д.). Кроме того, вы не можете использовать Qt из потока Python (вы не можете, например, отправить событие в основной поток через QApplication.postEvent): для работы вам нужен QThread.

    Общее правило может заключаться в использовании QThreads, если вы собираетесь каким-то образом взаимодействовать с Qt и использовать потоки Python в противном случае.

    И некоторые более ранние комментарии по этому вопросу от автора PyQt: «они оба обертки вокруг тех же реализаций собственных потоков». И обе реализации используют GIL таким же образом.

    Нити Python будут проще и безопаснее, и поскольку они предназначены для приложения на основе ввода-вывода, они могут обойти GIL. Тем не менее, рассматривали ли вы неблокирующий ввод-вывод с использованием витых или неблокирующих сокетов / select?

    EDIT: больше по потокам

    Нити Python

    Потоки Python – это системные потоки. Тем не менее, Python использует глобальную блокировку интерпретатора (GIL), чтобы гарантировать, что интерпретатор выполняет только определенный блок размера инструкций байтового кода за раз. К счастью, Python выпускает GIL во время операций ввода-вывода, делая потоки полезными для имитации неблокирующего ввода-вывода.

    Важное предупреждение: это может ввести в заблуждение, так как количество инструкций байт-кода не соответствует количеству строк в программе. Даже одно назначение не может быть атомарным в Python, поэтому блокировка мьютекса необходима для любого блока кода, который должен выполняться атомарно, даже с GIL.

    Потоки QT

    Когда Python отключает управление сторонним компилированным модулем, он выпускает GIL. В обязанности модуля входит обеспечение атомарности, когда это необходимо. Когда управление передается обратно, Python будет использовать GIL. Это может сделать использование сторонних библиотек в связи с запутанностью потоков. Еще сложнее использовать внешнюю библиотеку потоков, поскольку она добавляет неопределенность относительно того, где и когда управление находится в руках модуля и интерпретатора.

    Потоки QT работают с выпущенным GIL. Потоки QT могут выполнять код библиотеки QT (и другой код скомпилированного модуля, который не получает GIL) одновременно. Тем не менее, код Python, выполняемый в контексте потока QT, по- прежнему приобретает GIL, и теперь вам нужно управлять двумя наборами логики для блокировки вашего кода.

    В конце концов, как потоки QT, так и потоки Python являются обертками вокруг потоков системы. Потоки Python являются более безопасными для использования, поскольку те части, которые не написаны на Python (неявно использующие GIL), используют GIL в лю

    www.rupython.com

    multithreading пример — Threading в приложении PyQt:используйте потоки Qt или потоки Python?

    примеры qthread (7)

    Я пишу приложение GUI, которое регулярно извлекает данные через веб-соединение. Поскольку это извлечение занимает некоторое время, это приводит к тому, что пользовательский интерфейс не реагирует во время процесса поиска (его нельзя разделить на более мелкие части). Вот почему я хотел бы перенаправить веб-соединение на отдельный рабочий поток.

    [Да, я знаю, теперь у меня две проблемы .]

    В любом случае, приложение использует PyQt4, поэтому я хотел бы знать, какой лучший выбор: использовать потоки Qt или использовать модуль threading Python? Каковы преимущества / недостатки каждого? Или у вас есть совершенно другое предложение?

    Изменить (re bounty): Хотя решение в моем конкретном случае, вероятно, будет использовать неблокирующий сетевой запрос, например, предложенный Джеффом Обром и Лукашем Лалинским (так что в основном проблемы с параллелизмом для сетевой реализации) мне все же хотелось бы больше Углубленный ответ на общий вопрос:

    Каковы преимущества и недостатки использования потоков PyQt4 (то есть Qt) поверх собственных потоков Python (из модуля threading передачи)?

    Редактировать 2: Спасибо всем за ответы. Хотя нет 100% -ного соглашения, похоже, широко распространено мнение, что ответ «используется Qt», поскольку преимущество этого заключается в интеграции с остальной библиотекой, не создавая реальных недостатков.

    Для тех, кто хочет выбрать между двумя реализациями потоковой передачи, я настоятельно рекомендую вам прочитать все предоставленные здесь ответы, включая поток списков рассылки PyQt, на который ссылаются abbot.

    Было несколько ответов, которые я рассмотрел за щедрость; в конце концов я выбрал аббата для очень релевантной внешней ссылки; это был, однако, близкий звонок.

    Еще раз спасибо.

    code.i-harness.com

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *