Объектно-Ориентированное Программирование на c++,...

57
ВВЕДЕНИЕ В МУЛЬТИПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ C++

Upload: dima-dzuba

Post on 01-Jul-2015

374 views

Category:

Education


9 download

DESCRIPTION

ВВЕДЕНИЕ В МУЛЬТИПРОГРАММИРОВАНИЕ

TRANSCRIPT

Page 1: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

ВВЕДЕНИЕ В МУЛЬТИПРОГРАММИРОВАНИЕНА ЯЗЫКЕ C++

Page 2: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

одновременного выполнения задач.

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

Page 3: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

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

Page 4: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

SMP Shared Memory Processor или Symmetric MultiProcessor

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

Page 5: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Пример SMP:IBM HS23 Blade

Page 6: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

MPPMassively Parallel Processors

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

Page 7: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

NUMA Non-Uniform Memory Access

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

Page 8: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

Page 9: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

Page 10: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

Page 11: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Два вида мультирпограммированияMultiple processes Multiple threads

Page 12: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

Page 13: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

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

Page 14: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

Page 15: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

Page 16: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

Page 17: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

Page 18: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

Page 19: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

Page 20: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

Page 21: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Hello World!CPP_Examples18#include <iostream>

#include <thread>

void hello() {

std::cout<<"Hello Concurrent World\n";

}

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

std::thread t(hello); // launch new thread

t.join(); //wait for finish of t

cin.get();

return 0;

}

Page 22: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

std::threadстандарт С++11

Конструктор

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

Args&&... args);

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

void join();

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

Page 23: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Передаем объекты в потокCPP_Examples19

Задача: Нужно передать указатель на объект таким образом, что бы он гарантированно был удален в другом потоке выполнения.

Используем шаблон std::unique_ptr(<memory>)

std::unique_ptr – позволяет иметь только одну ссылку на объект. Его нельзя копировать.

Используем шаблон std::move (<utility>)

std::move – позволяет перемещать содержимое unique_ptr;

std::unique_ptr<int> p1(new int(5));

std::unique_ptr<int> p2 = p1;

//Compile error.

std::unique_ptr<int> p3 = std::move(p1); //Transfers ownership. p3 now owns the memory and p1 is rendered invalid.

p3.reset(); //Deletes the memory.

p1.reset(); //Does nothing.

Page 24: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

std::this_thread::get_id()

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

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

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

Page 25: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Как дождаться завершения потока красиво?CPP_Examples20

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

class Scoped_Thread

{

std::thread t;

public:

explicit Scoped_Thread(std::thread t_): t(std::move(t_))

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

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

};

Page 26: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

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

Page 27: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

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

Page 28: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

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

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

Page 29: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

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

Page 30: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

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

recursive_mutexможет войти «сам в себя»

timed_mutexв отличие от обычного мьютекса, имеет еще два метода: try_lock_for() и try_lock_until()

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

Page 31: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

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

unique_lockв отличие от lock_guard, также поддерживает отложенную блокировку и использование условных переменных

// default (1) unique_lock() noexcept;

std::unique_lock<std::mutex> lock_a(lhs.m,std::defer_lock);

std::unique_lock<std::mutex> lock_b(rhs.m,std::defer_lock);

std::lock(lock_a,lock_b);

Page 32: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

DeadlockCPP_Examples21_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);

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

Page 33: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

Правило:

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

Page 34: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

1. Исключения между потоками не передаются!

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

Page 35: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Условные переменные <condition_variable>CPP_Examples24,25

condition_variableтребует от любого потока перед ожиданием сначала выполнить std::unique_lock

condition_variable_anyболее общая реализация, которая работает с любым типом, который можно заблокировать

1. Должен быть хотя бы один поток, ожидающий, пока какое-то условие станет истинным. Ожидающий поток должен сначала выполнить unique_lock.

2. Должен быть хотя бы один поток, сигнализирующий о том, что условие стало истинным. Сигнал может быть послан с помощью notify_one(), при этом будет разблокирован один (любой) поток из ожидающих, или notify_all(), что разблокирует все ожидающие потоки.

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

Page 36: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Лямбда выражения

Page 37: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

LambdaCPP_Examples26

Лямбда-выражения в C++ — это краткая форма записи анонимных функторов.

Например:

[](int _n) { cout << _n << " ";}

Соответствует:

class MyLambda{

public: void operator ()(int _x) const { cout << _x << " "; } };

Page 38: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Лямбда функции могут возвращать значенияCPP_Examples27В случае, если в лямбда-функции только один оператор return то тип значения можно не указывать. Если несколько, то нужно явно указать.

[] (int i) -> double

{

if (i < 5)

return i + 1.0;

else if (i % 2 == 0)

return i / 2.0;

else

return i * i;

}

Page 39: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Захват переменных из внешнего контекстаCPP_Examples28

[] // без захвата переменных из внешней области видимости

[=] // все переменные захватываются по значению

[&] // все переменные захватываются по ссылке

[this] // захват текущего класса

[x, y] // захват x и y по значению

[&x, &y] // захват x и y по ссылке

[in, &out] // захват in по значению, а out — по ссылке

[=, &out1, &out2] // захват всех переменных по значению, кроме out1 и out2,

// которые захватываются по ссылке

[&, x, &y] // захват всех переменных по ссылке, кроме x…

Page 40: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Генерация лямбда-выраженийCPP_Examples29Начиная со стандарта C++11 шаблонный класс std::function является полиморфной оберткой функций для общего использования. Объекты класса std::function могут хранить, копировать и вызывать произвольные вызываемые объекты - функции, лямбда-выражения, выражения связывания и другие функциональный объекты. Говоря в общем, в любом месте, где необходимо использовать указатель на функцию для её отложенного вызова, или для создания функции обратного вызова, вместо него может быть использован std::function, который предоставляет пользователю большую гибкость в реализации.

Впервые данный класс появился в библиотеке Function в версии Boost 1.23.0[7]. После его дальнейшей разработки, он был включен в стандарт расширения C++ TR1 и окончательно утвержден в С++11.

Определение класса

template<class> class function; // undefined

template<class R, class... ArgTypes> class function<R(ArgTypes...)>;

Page 41: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

Page 42: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

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

Например операция «++» не является атомарной:

int x = 0;

++x;

Транслируется в ассемблерный код, примерно так:

013C5595 mov eax,dword ptr [x]

013C5598 add eax,1

013C559B mov dword ptr [x],eax

Page 43: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Атомарные типы C++#include<atomic>

std::atomic_bool //bool

std::atomic_char //char

std::atomic_schar //signed char

std::atomic_uchar //unsigned char

std::atomic_int //int

std::atomic_uint //unsigned int

std::atomic_short //short

std::atomic_ushort //unsigned short

std::atomic_long //long

std::atomic_ulong //unsigned long

std::atomic_llong //long long

std::atomic_ullong //unsigned long long

std::atomic_char16_t //char16_t

std::atomic_char32_t //char32_t

std::atomic_wchar_t //wchar_t

std::atomic_address //void*

Page 44: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Основные операцииload() //Прочитать текущее значение

store() //Установить новое значение

exchange() //Установить новое значение и вернуть предыдущее

compare_exchange_weak() // см. следующий слайд

compare_exchange_strong() // compare_exchange_weak в цикле

fetch_add() //Аналог оператора ++

fetch_or() //Аналог оператора --

is_lock_free() //Возвращает true, если операции на данном типе

неблокирующие

Page 45: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Метод atomic::compare_exchange_weakbool compare_exchange_weak( Ty& Exp, Ty Value)

Сравнивает значения которые хранится в *this с Exp.

• Если значения равны то операция заменяет значение, которая хранится в *this на Val(*this=val) , с помощью операции read-modify-write.

• Если значения не равны, то операция использует значение, которая хранится в *this, чтобы заменить Exp (exp=this).

Page 46: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Потокобезопасный StackCPP_Examples30

void push(const T& data)

{

node* new_node = new node(data, head.load());

while (!head.compare_exchange_weak(

new_node->next,

new_node));

}

Page 47: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Что еще почитать?C++ Concurrency in ActionPractical MultithreadingAnthony Williams

February, 2012 | 528 pages ISBN: 9781933988771

Разные блоги, например:

http://habrahabr.ru/post/182610/

Page 48: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

TODO

Page 49: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Memory Wall

Page 50: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Memory Hierarchy

Page 51: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Cache line

Page 52: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

False sharing

Page 53: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Memory padding

Page 54: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Thread affinity

Page 55: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

Отношение happens-before

Page 56: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

data-race

Page 57: Объектно-Ориентированное Программирование на C++, Лекции 7 и 8

volatile• В C и C++ есть ключевое слово volatile, которое указывает компилятору, что значение в

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

• Будет удалено компилятором:

for( char* ptr = start; ptr < start + size; ptr += MemoryPageSize ) { *ptr; }

• Не будет удалено компилятором:

for( volatile char* ptr = start; ptr < start + size; ptr += MemoryPageSize ) { *ptr; }