27
Темы лекции: Многопоточность в Qt. Практическое задание: Многопоточность в Qt. Тренер: Игорь Шкулипа, к.т.н. С++ Библиотеки STL и Qt. Занятие 8

C++ STL & Qt. Занятие 08

Embed Size (px)

Citation preview

Темы лекции: Многопоточность в Qt.

Практическое задание: Многопоточность в Qt.

Тренер: Игорь Шкулипа, к.т.н.

С++ Библиотеки STL и Qt. Занятие 8

http://www.slideshare.net/IgorShkulipa 2

Object Pool

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

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

http://www.slideshare.net/IgorShkulipa 3

Реализация Object Pool на основе Singleton. Классы объектов

class IObject {

protected:

string _strText;

public:

virtual void Print() {

cout<<"The Object is: "<<_strText.c_str()<<"\n";

} };

class Object1: public IObject {

public:

Object1(){

_strText="Object 1";

} };

class Object2: public IObject {

public:

Object2(){

_strText="Object 2";

} };

class Object3: public IObject {

public:

Object3(){

_strText="Object 3";

} };

class Object4: public IObject {

public:

Object4(){

_strText="Object 4";

} };

http://www.slideshare.net/IgorShkulipa 4

Object Pool

template<unsigned poolSize> class ObjectPool{

public:

static ObjectPool* GetInstance() {

if (!_instance) _instance=new ObjectPool();

return _instance;

}

IObject* GetObject() {

for (unsigned i=0;i<poolSize;i++)

{ if (!_busyObjects[i]){

_busyObjects[i]=true;

return _objectPool[i];

} }

return NULL;

}

void ReleaseObject(IObject* object){

for (unsigned i=0;i<poolSize;i++)

{ if (_objectPool[i]==object){

_busyObjects[i]=false;

} }

}

...

http://www.slideshare.net/IgorShkulipa 5

Object Pool...

private:

ObjectPool(){

for (unsigned i=0;i<poolSize;i++)

{

unsigned iObjNumber=rand()%4;

switch (iObjNumber)

{

case 0: _objectPool[i]=new Object1(); break;

case 1: _objectPool[i]=new Object2(); break;

case 2: _objectPool[i]=new Object3(); break;

case 3: _objectPool[i]=new Object4(); break;

}

_busyObjects[i]=false;

}

}

private:

IObject* _objectPool[poolSize];

bool _busyObjects[poolSize];

static ObjectPool* _instance;

};

http://www.slideshare.net/IgorShkulipa 6

Использование Object Pooltemplate<unsigned poolSize>

ObjectPool<poolSize>* ObjectPool<poolSize>::_instance=NULL;

int main()

{

ObjectPool<5>* op=ObjectPool<5>::GetInstance();

IObject* object1=op->GetObject();

if (object1) object1->Print(); else cout<<"The Object is: NULL\n";

IObject* object2=op->GetObject();

if (object2) object2->Print(); else cout<<"The Object is: NULL\n";

IObject* object3=op->GetObject();

if (object3) object3->Print(); else cout<<"The Object is: NULL\n";

IObject* object4=op->GetObject();

if (object4) object4->Print(); else cout<<"The Object is: NULL\n";

IObject* object5=op->GetObject();

if (object5) object5->Print(); else cout<<"The Object is: NULL\n";

IObject* object6=op->GetObject();

if (object6) object6->Print(); else cout<<"The Object is: NULL\n";

IObject* object7=op->GetObject();

if (object7) object7->Print(); else cout<<"The Object is: NULL\n";

op->ReleaseObject(object2);

IObject* object8=op->GetObject();

if (object8) object8->Print(); else cout<<"The Object is: NULL\n";

}

http://www.slideshare.net/IgorShkulipa 7

Результат

The Object is: Object 2

The Object is: Object 4

The Object is: Object 3

The Object is: Object 1

The Object is: Object 2

The Object is: NULL

The Object is: NULL

The Object is: Object 4

http://www.slideshare.net/IgorShkulipa 8

Преимущества и недостатки

◦ Пул объектов отслеживает объекты, которые он создает.

◦ Паттерн Object Pool может использоваться для инкапсуляциилогики создания объектов. Однако он не управляет имипосле их создания.

◦ Достоинством этого паттерна является быстрое созданиеобъектов, однако это реализовано за счет использованиябольших ресурсов памяти.

http://www.slideshare.net/IgorShkulipa 9

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

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

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

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

http://www.slideshare.net/IgorShkulipa 10

QProcess

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

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

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

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

http://www.slideshare.net/IgorShkulipa 11

QThread

Для использования многопоточности можно унаследовать класс от QThread иперезаписать метод run(), в который должен быть помещен код для исполненияв потоке. Чтобы запустить поток, нужно вызвать метод start().

class CustomThread : public QThread

{

public:

CustomThread();

void setMessage(const QString &message);

void run() {/*Действия потока*/};

void stop();

private:

QString messageStr;

volatile bool stopped;

};

//...

int main(int argc, char** argv)

{

CustomThread thread;

thread.start();

}

http://www.slideshare.net/IgorShkulipa 12

Приоритет потоков

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

• в первую входят четыре наиболее часто применяемых приоритета. Ихзначимость распределяется по возрастанию — IdlePriority, LowestPriority,LowPriority, NormaiPriority. Они подходят для решения задач, которымпроцессор требуется только время от времени, например, для фоновойпечати или для каких-нибудь несрочных действий;

• во вторую группу входят два приоритета — HighPriority, HighestPriority.Пользуйтесь такими приоритетами с большой осторожностью. Обычно этипотоки большую часть времени ожидают какие-либо события;

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

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

CustomThread thread;

thread.start(QThread::IdlePriority);

http://www.slideshare.net/IgorShkulipa 13

Синхронизация

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

Синхронизация позволяет задавать критические секции (criticalsections), к которым в определенный момент имеет доступ только одиниз потоков. Это гарантирует то, что данные ресурса, контролируемыекритической секцией, будут невидимы другими потоками и они неизменят их. И только после того, как поток выполнит всюнеобходимую работу, он освобождает ресурс, и, затем, доступ к этомуресурсу может получить любой другой поток.

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

http://www.slideshare.net/IgorShkulipa 14

Объекты синхронизации Mutex

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

Механизм мьютексов реализован классом QMutex. Метод lock() класса QMutexпроизводит блокировку ресурса. Для обратной операции существует методunlock(), который открывает закрытый ресурс для других потоков.

Класс QMutex также содержит метод tryLock(). Этот метод можно использовать длятого, чтобы проверить, заблокирован ресурс или нет. Этот метод неприостанавливает исполнение потока и возвращается немедленно, со значениемfalse, если ресурс уже захвачен другим потоком, и не ожидает егоосвобождения. В случае успешного захвата ресурса этот метод вернет true.

В сложных функциях, особенно при использовании исключений C++, легко можноошибиться при выполнении последовательностей операций позапиранию/отпиранию мьютексов. Поэтому, в состав Qt включен классQMutexLocker, который значительно упрощает работу с мьютексами.Конструктор класса QMutexLocker принимает объект QMutex в видеаргумента и запирает его. Деструктор класса QMutexLocker - отпираетмьютекс.

http://www.slideshare.net/IgorShkulipa 15

Пример «Потокобезопасный стек»

template <class Type>

class ThreadSafeStack

{

public:

void push(const Type& val)

{

mutex.lock();

stack.push(val);

mutex.unlock();

}

Type pop()

{

QMutexLocker locker (&mutex);

return stack.empty() ? Type() : stack.pop();

}

private:

QMutex mutex;

QStack<Type> stack;

};

http://www.slideshare.net/IgorShkulipa 16

QWaitCondition

Библиотека Qt предоставляет класс QWaitCondition, обеспечивающийвозможность координации потоков.

Если поток намеревается дождаться разблокировки ресурса, то онвызывает метод QWaitCondition::wait() и, тем самым, входит врежим ожидания. Выводится он из этого режима в том случае, еслипоток, который заблокировал ресурс, вызовет методQWaitCondition::wakeOne() или QWaitCondition::wakeAll().

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

http://www.slideshare.net/IgorShkulipa 17

Семафоры

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

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

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

http://www.slideshare.net/IgorShkulipa 18

Взаимная блокировка

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

В итоге — оба ждут друг друга. Если ни один из этих потоков не освободит занятыйим ресурс, то оба "зависнут" и не смогут продолжать свою работу дальше.

Это явление получило название взаимной блокировки (deadlock).

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

http://www.slideshare.net/IgorShkulipa 19

Обмен сообщениями между потоками

Один из важнейших вопросов при многопоточном программировании —это обмен сообщениями.

Каждый поток может иметь свой собственный цикл событий. Благодаряэтому можно осуществлять связь между объектами. Такая связь можетпроизводиться двумя способами: при помощи соединения сигналов ислотов или обмена событиями.

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

Обработка событий производится из контекста принадлежности объекта кпотоку, то есть обработка его событий будет производиться в томпотоке, которому объект принадлежит. Объекты можно перемещать изодного потока в другой с помощью методаQObject::moveToThread().

http://www.slideshare.net/IgorShkulipa 20

Отправка событий

Отправка событий — это еще одна из возможностей для осуществлениясвязи между объектами. Есть два метода для высылки событий -QCoreApplication::postEvent() и QCoreApplication::sendEvent().

Для того чтобы объект потока был в состоянии обрабатывать получаемыесобытия, в классе потока нужно реализовать метод QObject::event().

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

http://www.slideshare.net/IgorShkulipa 21

Сигналы и слоты

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

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

http://www.slideshare.net/IgorShkulipa 22

Пример «Спам-бот». См. Занятие №8.#ifndef SPAMBOT_H

#define SPAMBOT_H

#include <QObject>

#include <QString>

#include <QTime>

#include "chatmessage.h"

class SpamBot: public QObject

{

Q_OBJECT

public:

SpamBot();

void stop();

void run();

public slots:

void handleRun();

signals:

void sendSpam();

private:

volatile bool stopped;

};

#endif // SPAMBOT_H

http://www.slideshare.net/IgorShkulipa 23

Реализация класса

#include "spambot.h"

SpamBot::SpamBot() {

stopped=false;

}

void SpamBot::stop() {

stopped=true;

}

void SpamBot::run() {

while(!stopped)

{

QTime endTime= QTime::currentTime().addSecs(3);

while( QTime::currentTime() < endTime )

{

//Waiting...

}

emit sendSpam();

}

}

void SpamBot::handleRun() {

this->run();

}

http://www.slideshare.net/IgorShkulipa 24

Дополненный презентер

class ChatClientPresenter: public QObject

{

Q_OBJECT

public:

ChatClientPresenter(ChatClient* cl, IView* v);

ChatClientPresenter(IView* v);

void emitRunBot()

{

emit runBot();

}

signals:

void runBot();

private slots:

void DisplayMessage();

void SendMessage();

void SendMessageSpam();

private:

ChatClient* client;

IView* view;

};

void ChatClientPresenter::SendMessageSpam()

{

ChatMessage message("SpamBot","Spam");

client->sendToServer(message);

}

http://www.slideshare.net/IgorShkulipa 25

Функция main#include <QtGui/QApplication>

#include "mainwindow.h"

#include "chatclientpresenter.h"

#include "spambot.h"

#include <QThread>

int main(int argc, char *argv[]) {

QApplication a(argc, argv);

MainWindow* view=new MainWindow();

ChatClientPresenter* present=new ChatClientPresenter(view);

view->show();

QThread *thread=new QThread();

SpamBot *spamBot=new SpamBot();

QObject::connect(spamBot, SIGNAL(sendSpam()),

present,SLOT(SendMessageSpam()),Qt::QueuedConnection);

QObject::connect(present, SIGNAL(runBot()),

spamBot, SLOT(handleRun()), Qt::QueuedConnection);

spamBot->moveToThread(thread);

thread->start();

present->emitRunBot();

return a.exec();

}

http://www.slideshare.net/IgorShkulipa 26

Результат

http://www.slideshare.net/IgorShkulipa 27

Лабораторная работа №8. Многопоточность

Создать в тетрисе дополнительный поток, который с определеннойпериодичностью будет обновлять рейтинг пользователей на форме(см. Лабораторную работу №5).