43
ВВЕДЕНИЕ В МУЛЬТИПРОГРАММИРОВАНИЕ ЛЕКЦИЯ 13-14

Объектно-ориентированное программирование. Лекции 13 и 14

Embed Size (px)

Citation preview

ВВЕДЕНИЕВМУЛЬТИПРОГРАММИРОВАНИЕЛЕКЦИЯ13-14

Основныепонятия• Мультипроцессирование - использованиенесколькихпроцессоровдляодновременноговыполнениязадач.

• Мультипрограммирование - одновременноевыполнениенесколькихзадачнаодномилинесколькихпроцессорах.

МультипроцессированиеМультипроцессированиебываетнесколькихвидов:

• когдаиспользуютсяразличныевидыоборудования,например,одновременнаяработацентральногопроцессораиграфическогоускорителявидеокарты;

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

Вовторомслучаебываетдваподхода:

• свыделениемуправляющегоиподчиненныхустройств(асимметричноемультипроцессирование)

• сиспользованиемполностьюравноправных(симметричноемультипроцессирование).

SMPShared Memory Processor илиSymmetric MultiProcessorВтакихкомпьютерахнесколькопроцессоровподключеныкобщейоперативнойпамятииимеюткнейравноправныйиконкурентныйдоступ.Помереувеличениячислапроцессоровпроизводительностьоперативнойпамятиикоммутаторов,связывающихпроцессорыспамятью,становитсякритическиважной.ОбычновSMPиспользуются2-8процессоров;режечислопроцессоровдостигаетдесятков.Взаимодействиеодновременновыполняющихсяпроцессовосуществляетсяпосредствомиспользованияобщейпамяти,ккоторойимеютравноправныйдоступвсепроцессоры

ПримерSMP:IBMHS23Blade

MPPMassively Parallel ProcessorsВMPPиспользуютнесколькооднопроцессорныхилиSMP-систем,объединяемыхспомощьюнекоторогокоммуникационногооборудованиявединуюсеть.Приэтомможетприменятьсякакспециализированнаявысокопроизводительнаясредапередачиданных,такиобычныесетевыесредства- типаEthernet.ВMPPсистемахоперативнаяпамятькаждогоузлаобычноизолированаотдругихузлов,идляобменаданнымитребуетсяспециальноорганизованнаяпересылкаданныхпосети.ДляMPPсистемкритическойстановитсясредапередачиданных;однаковслучаемалосвязанныхмеждусобойпроцессоввозможноодновременноеиспользованиебольшогочислапроцессоров.ЧислопроцессороввMPPсистемахможетизмерятьсясотнямиитысячами.

NUMANon-UniformMemoryAccessЯвляетсякомпромиссоммеждуSMPиMPPсистемами:оперативнаяпамятьявляетсяобщейиразделяемоймеждувсемипроцессорами,ноприэтомпамятьнеоднороднаповременидоступа.Каждыйпроцессорныйузелимеетнекоторыйобъемоперативнойпамяти,доступккоторойосуществляетсямаксимальнобыстро;длядоступакпамятидругогоузлапотребуетсязначительнобольшевремени.

Процессыипотоки• Программа(program)– этопоследовательностькоманд,реализующаяалгоритм

решениязадачи.

• Процесс(process)– этопрограмма(пользовательскаяилисистемная)входевыполнения.

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

• Процессможетсодержатьодинилинесколькопотоков(thread)– объектов,которымоперационнаясистемапредоставляетпроцессорноевремя.Сампосебепроцессневыполняется– выполняютсяегопотоки.Такимобразом,машинныекоманды,записанныевисполняемомфайле,выполняютсянапроцессоревсоставепотока.Еслипотоковнесколько,онимогутвыполнятьсяодновременно.

Мультипрограммирование• Вмультипрограммированииключевымместом

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

• Повременипланированияможновыделитьстатическоеидинамическоесоставлениерасписания.• Пристатическомпланированиирасписаниесоставляется

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

• Вслучаединамическогопланированияпорядокзапусказадачипередачиуправлениязадачамопределяетсянепосредственнововремяисполнения.

Планированиезадач

МультипроцессированиеvsМультипрограммирование

Нескольковычислительныхядерпроцессорапозволяютвыполнятьнесколькозадачодновременно.

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

ДвавидамультирпограммированияMultipleprocesses Multiplethreads

Двавидауправлениямногозадачностью• вслучаеневытесняющей

многозадачностирешениеопереключениипринимаетвыполняемаявданныймоментзадача

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

Планировщикзадач• Вытесняющаямногозадачностьпредполагаетналичиенекоторогоарбитра,принадлежащего

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

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

• Перепланирование:• Еслисменаприоритетавызываетперепланирование - значит,этосистемасабсолютными

приоритетами ивытесняющеймногозадачностью.• Еслимоментыперепланированиянаступаютпоисчерпаниювременных квантов(возможно

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

Комбинированноеперепланированиезадач

Причиныизмененияприоритета• запускзадачи(длявозможноскорейшегоначалаисполнения);

• досрочноеосвобождениепроцессорадоисчерпанияотведенногокванта(великшанс,чтозадачаивэтотразтакжебыстроотдаступравление);

• частыйвызовоперацийввода-вывода(приэтомзадачачащенаходитсявожиданиизавершенияоперации,нежелизанимаетпроцессорноевремя);

• продолжительноеожиданиевочереди(приоритетожидающейзадачичастопостепенноначинаютувеличивать);

ЖизненныйциклпотокавWindows

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

Вытесняющийпланировщик• Унасестьдвавычислительныхпотока,выполняющих

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

• instruction pointer иstack pointer—хранятсяврегистрахпроцессора.Кроменихдлякорректнойработынеобходимаидругаяинформация,содержащаясяврегистрах:флагисостояния,различныерегистрыобщегоназначения,вкоторыхсодержатсявременныепеременные,итакдалее.Всеэтоназываетсяконтекстомпроцессора.

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

Текущаяпрограммапрерываетсяпроцессоромврезультатереакциинавнешнеесобытие— аппаратноепрерывание— ипередаетуправлениепланировщику

Зачемприменятьмногозадачность?1. Разделениепрограммынанезависимыечасти.Одинпроцессвыполняетоднузадачу

(например,взаимодействиеспользователем),адругой– другую(например,вычисления).

2. Дляувеличенияпроизводительности.

Увеличениечислапараллельныхпроцессовневсегдаприводиткускорениюпрограммы.

HelloWorld!Example99_Thread1. #include <iostream>2. #include <thread>3. void hello() {4. std::cout<<"Hello Concurrent World\n";5. }

6. int main(int argc,char * argv[]){7. std::thread t(hello); // launch new thread8. t.join(); //wait for finish of threads

9. cin.get();10.return 0;11.}

std::threadстандартС++11Конструктор

template <class Fn, class... Args> explicit thread (Fn&& fn, Args&&... args);

Принимаетвкачествеаргументафункториегопараметры.

void join();

Ожидаеткогдавыполнениепотоказакончится.

Какпередатьобъект?Example100_ThreadFunctor1. #include <thread>2. #include <iostream>3. class MyClass{4. public:5. // thread вызовет переопределенный оператор6. void operator()(const char* param){7. std::cout << param << std::endl;8. }9. };10. int main() {11. // передается копия объекта!12. std::thread my_thread(MyClass(),"Hello world!");13. my_thread.join();14. return 0;15. }

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

Ссемантикойперемещениямы100%уверенычтокданнымнашегопотоканебудетдоступаиздругихпотоков.

class MyClass {

protected:

std::unique_ptr<Param> ptr;

public:

MyClass( std::unique_ptr<Param> &&param) {

ptr = std::move(param);}

MyClass(MyClass &&other) { // конструктор перемещения

ptr = std::move(other.ptr);}

MyClass(const MyClass &) = delete;

MyClass & operator=(const MyClass &)= delete;

void operator()() { } // тут код потока

};

Полезныефункцииstd::this_threadstd::thread::hardware_concurrency()

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

std::this_thread::get_id()

Возвращаетидентификаторпотока.

std::this_thread::sleep_for(std::chrono::milliseconds)

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

Какдождатьсязавершенияпотокакрасиво?Example104_ScopedThreadclass Scoped_Thread {std::thread t;public:Scoped_Thread(std::thread&& t_) : t(std::move(t_)) {if (!t.joinable()) throw std::logic_error("No thread");

};Scoped_Thread(std::thread& t_) : t(std::move(t_)) {if (!t.joinable()) throw std::logic_error("No thread");

};Scoped_Thread(Scoped_Thread & other) : t(std::move(other.t)) { };Scoped_Thread(Scoped_Thread && other) : t(std::move(other.t)) {};Scoped_Thread& operator=(Scoped_Thread &&other) {t = std::move(other.t); return *this;}

~Scoped_Thread() {if (t.joinable()) t.join();};

};

Проблемыработысдинамическими структурамиданныхвмногопоточнойсреде

Приудаленииэлементаизсвязанногоспискапроизводитсянесколькоопераций:

- удалениесвязиспредыдущимэлементом

- удалениесвязисоследующимэлементом

- удалениесамогоэлементасписка

Вовремявыполненияэтихоперацийкэтимиэлементамиобращатьсяиздругихпотоковнельзя!

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

Основныеметодыдостижения:

• Реентрабельность;

• Локальноехранилищепотока;

• Взаимноеисключение;

• Атомарныеоперации;

РеентерабельностьСвойствофункции,предполагающеееёкорректноеисполнениевовремяповторноговызова(например,рекурсивного).

• Недолжнаработатьсостатическими(глобальнымиданными);

• Недолжнавозвращатьадресстатических(глобальныхданных);

• Должнаработатьтолькосданными,переданнымивызывающейстороной;

• Недолжнамодифицироватьсвоегокода;

• Недолжнавызыватьнереентерабельныхпрограммипроцедур.

Потоконебезопасный кодExample105_RaceConditionvoid add_function( long * number){for( long i=0;i<1000000000L;i++) (*number)++;}

void subst_function( long * number){for( long i=0;i<1000000000L;i++) (*number)--;}

int main() {long number = 0;{Scoped_Thread th1(std::move(std::thread(add_function,&number))); Scoped_Thread th2(std::move(std::thread(subst_function,&number)));}// Результат неопределен!std::cout << "Result:" << number << std::endl;return 0;}

ВзаимноеисключениеExample108_Mutex1Мьютекс—базовыйэлементсинхронизацииивС++11представленв4формахвзаголовочномфайле<mutex>:

mutexобеспечиваетбазовыефункцииlock()иunlock()инеблокируемыйметодtry_lock()

timed_mutexвотличиеотобычногомьютекса,имеетещедваметода:try_lock_for()иtry_lock_until()позволяющихконтролироватьвремяожиданиявхождениявmutex

РекурсивноевхождениеMutexExample109_Mutex21. recursive_mutex

можетвойти«самвсебя»,т.е.Поддерживаетмногократныйвызовlockизодногопотока.Содержитвсефункцииmutex.

2. recursive_timed_mutexэтокомбинацияtimed_mutex иrecursive_mutex

Замки

lock_guardExample110_LockGuard1. Захват в конструкторе2. Освобождение в деструкторе3. Используются методы мьютексов

◦ Захват:voidlock();◦ Освободить:voidunlock();

unique_guardExample111_UniqueLock1. Тоже,чтоlock_guard2. Используютсяметодымьютексов

◦ Попытатьсязахватить:booltry_lock();

◦ Захватсограничением:voidtimed_lock(...);

3. +Дополнительныефункцииполучениямьютекса,проверки«захваченности»...

ОпасностивмногопоточноесредеExample101_RaceConditionint array[100];

std::thread s1(sort, array, 100, less);

std::thread s2(sort, array, 100, more);

s1.join();

s2.join();

Когданесколькопотоковимеютдоступкоднимитемжеданным(бесконтрольный),торезультатработыалгоритманеопределен!

ЛокальноехранилищепотокаExample107_TLC• Наборстатических/глобальных переменных,локальныхпоотношениюк

данномупотоку.

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

Вообщеговоря,вданномпримереTLSнесовсемпотокобезопасный.

Потоко-безопасныйStackExample112_ThreadSafeStackКлассы«обертки»позволяютнепротиворечивоиспользоватьмьютекс вRAII-стилесавтоматическойблокировкойиразблокировкойврамкаходногоблока.Этиклассы:

lock_guardкогдаобъектсоздан,онпытаетсяполучитьмьютекс (вызываяlock()),акогдаобъектуничтожен,онавтоматическиосвобождаетмьютекс (вызываяunlock())

Печатьнаэкранэтотожеразделяемыйресурс– такчтопечатьтожезащищаеммьютексом.

DeadlockExample113_Deadlockstd::lock_guard<std::mutex> lock(a);

std::lock_guard<std::mutex> lock(b);

std::lock_guard<std::mutex> lock(b);

std::lock_guard<std::mutex> lock(a);

Возникаеткогданесколькопотоковпытаютсяполучитьдоступкнесколькимресурсамвразнойпоследовательности.

ПримеропаснойситуацииExample114_PassOutПередачаданныхиззаграницуlock.

Правило:

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

ExceptionsвмногопоточнойсредеExample115_Exception

1. Исключениямеждупотокаминепередаются!2. Нужноустроитьхранилищеисключений,длятогочтобыихпотомобработать!

future+asyncExample117_Futurefuture– служитдляполучениярезультатавычисленийиздругогопотока.

Т.е.Функциявыполняемаяthread теперьможетвозвращатьзначение.

– этошаблон(параметр– типвозвращаемогозначения)

– конструируетсяспомощьюstd::async

— результатвыполненияполучаетсяметодомget()

— asyncзапускаетпотокисинхронизируетрезультатсвозвращаемымfuture

T function() {

return T();

}

int main (){

std::future<T> fut = std::async (function);

T x = fut.get();

}

futureработаетнаpromiseExample118_Promisetemplate<classT>promise– шаблонкоторыйсохраняетзначениетипаT,котороеможетбытьполученоспомощьюfutureget_future – запросfuture,связанногосозначениемвнутриpromiseset_value – устанавливаетзначение(ипередаетеговвызовget_future)set_exception – передачаисключения

Спасибо!ВСЕИДЕМНАПЕРЕРЫВ