177
Лекция 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home Параллельные вычислительные технологии Осень 2014 (Parallel Computing Technologies, PCT 14)

ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Embed Size (px)

DESCRIPTION

ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014 Сибирский государственный университет телекоммуникаций и информатики преподаватель: Пазников Алексей Александрович к.т.н., доцент кафедры вычислительных систем СибГУТИ

Citation preview

Page 1: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Лекция 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты

Пазников Алексей АлександровичКафедра вычислительных систем СибГУТИ

Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home

Параллельные вычислительные технологииОсень 2014 (Parallel Computing Technologies, PCT 14)

Page 2: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Создание и завершение работы потоков

2

Page 3: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

О дивный новый [параллельный] мир!

#include <iostream>#include <thread>

void hello() { // функция, которая реализует поток std::cout << "hello brave new world!\n";}

int main() { std::thread t(hello); // создаём поток t.join(); // дожидаемся завершения}

3

Page 4: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Запуск потока

class thread_class { // класс с перегруженным оператором()public: void operator()() const { hello(); bye(); }}

#include <iostream>#include <thread>

void hello() { std::cout << "hello brave new world!\n";}

int main() { std::thread t(hello); t.join();}

4

Page 5: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Запуск потока

class thread_class {public: void operator()() const { hello(); bye(); }}

std::thread thr((thread_class())); // зачем ()?std::thread thr{thread_class} // так лучше!

std::thread thr([](){ std::cout << "hello world\n";});thr.join();

Most vexing parse

5

Page 6: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Отсоединённый поток

struct func { int &i;

func(int &_i): i{_i} {}

void operator()() { std::cout << i << std::endl;// доступ к висячей сслыке }};

int main(int argc, const char *argv[]){ int local = 100; func myfunc(local); std::thread thr(myfunc); thr.detach(); // отсоединяем поток...} // поток ещё работает!

6

Page 7: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание завершения потока в случае исключения

std::thread thr(myfunc);

try { std::cout << "hello"; // ... throw "error"; }

catch (...) { thr.join(); // не забыть дождаться завершения std::cout << "exception catched\n"; return 1; }

thr.join();

7

Page 8: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание завершения потока в случае исключения

class thread_guard { std::thread &t;public: explicit thread_guard(std::thread &_t): t{_t} {} ~thread_guard() { if (t.joinable()) { t.join(); } } thread_guard(thread_guard const&) = delete; thread_guard &operator=(thread_guard const&) = delete;};

void foo() { int local; std::thread t{func(local)}; thread_guard g(t);} // t.join()

8

Page 9: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Запуск нескольких потоков и ожидание завершения

int main() { std::vector<std::thread> threads;

for (auto i = 0; i < 10; i++) { threads.push_back(std::thread([i](){ std::cout << i << "\n"; })); }

for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));}

9

Page 10: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Запуск нескольких потоков и идентификаторы потоков

std::vector<std::thread> threads;std::map<std::thread::id, int> table;

for (auto i = 0; i < 10; i++) { threads.push_back(std::thread([i](){ std::this_thread::sleep_for( std::chrono::milliseconds(100 * i)); std::cout << i << "\n"; })); table.insert(std::make_pair(threads.back().get_id(), i % 2));}

std::cout << "value of 5: " << table[threads[5].get_id()] << std::endl;std::cout << "value of 6: " << table[threads[6].get_id()] << std::endl;

for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); 10

Page 11: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача аргументов функции потока

void func(int i, std::string const &s1, std::string const &s2) { std::cout << s1 << " " << s2 << std::endl;}

int main() { std::thread t(func, 2014, "hello", "world"); t.join();}

11

Page 12: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача аргументов функции потока

void func(int i, std::string const &s1, std::string const &s2) { std::cout << s1 << " " << s2 << std::endl;}

int main() { char buf[] = "hello"; std::thread t(func, 2014, buf, "world"); t.detach();}

12

Page 13: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача аргументов функции потока

void func(int i, std::string const &s1, std::string const &s2) { std::cout << s1 << " " << s2 << std::endl;}

int main() { char buf[] = "hello"; // автоматическая переменная std::thread t(func, 2014, buf, "world"); t.detach();} // переменной foo нет, // а поток продолжает выполняться

Использование висячего указателя

13

Page 14: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача аргументов функции потока

void func(int i, std::string const &s1, std::string const &s2) { std::cout << s1 << " " << s2 << std::endl;}

int main() { char buf[] = "hello"; std::thread t(func, 2014, std::string(buf), "world"); t.detach();}

Явное преобразование позволяет избежать висячего указателя

14

Page 15: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача аргументов функции потока

void func(std::string &s_arg) { s_arg = "hello parallel world";}

int main() { std::string s{"hello world"}; std::thread t(func, s); t.join(); std::cout << s << std::endl; // результат операции? return 0;}

15

Page 16: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

void func(std::string &s_arg) { s_arg = "hello parallel world";}

int main() { std::string s{"hello world"}; std::thread t(func, std::ref(s)); // передача по ссылке! t.join(); std::cout << s << std::endl; // hello parallel world return 0;}

16

Page 17: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача управления потоком

struct bulky { // некий массивный объект std::string name; void print() { std::cout << "I'm " << name << std::endl; }};

void func(std::unique_ptr<bulky> obj) { obj->print();}

int main() { std::unique_ptr<bulky> ptr{new bulky{"Ivan"}}; std::thread t(func, ptr); t.join();}

17

Page 18: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача аргументов

struct bulky { // некий массивный объект std::string name; void print() { std::cout << "I'm " << name << std::endl; }};

void func(std::unique_ptr<bulky> obj) { obj->print();}

int main() { std::unique_ptr<bulky> ptr{new bulky{"Ivan"}}; std::thread t(func, std::move(ptr)); t.join();}

18

Page 19: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача управления потоком

void foo() { }void bar() { }

int main() { std::thread t1(foo); std::thread t2 = std::move(t1); // перемещение t1 = std::thread(bar); std::thread t3; t1 = std::move(t3); // ошибка! t3 = std::move(t2); t3 = std::move(t2); // ошибка!

t1.join(); t2.join(); // ошибка! t3.join();}

19

Page 20: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача управления потоком

std::thread foo() { std::thread thr([](){ std::cout << "thread\n"; }); return thr; // перемещение}

void bar(std::thread thr) { thr.join(); }

int main() { std::thread thr = foo(); bar(std::move(thr)); // перемещение

return 0;}

20

Page 21: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Передача управления потоком

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(); } scoped_thread(thread_guard const&) = delete; scoped_thread &operator=(thread_guard const&) = delete;};

void foo() { int local; scoped_thread t{std::thread(func((local))};}

21

Page 22: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Параллельная версия алгоритма accumulate

Поток 1 Поток 2 Поток num_threads

CPU CPU CPU

accumulate_block accumulate_block accumulate_block

22

Page 23: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Параллельная версия алгоритма accumulate

const int SIZE = 10;

template<typename Iterator, typename T, typename BinOperation>struct accumulate_block { // каждый поток рассчитывает свой блок void operator()(Iterator first, Iterator last, T& result, BinOperation op) { result = std::accumulate(first, last, result, op); }};

23

Page 24: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Параллельная версия алгоритма accumulate

template<typename Iterator, typename T, typename BinOp>T parallel_accumulate(Iterator first, Iterator last, T init, BinOp op) { unsigned long const length = std::distance(first, last);

if (!length) return init;

unsigned long const min_per_thread = 2;

unsigned long const max_threads = (length + min_per_thread - 1) / min_per_thread;

unsigned long const hardware_threads = std::thread::hardware_concurrency();

unsigned long const num_threads = std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);

unsigned long const block_size = length / num_threads;

std::vector<T> results(num_threads); std::vector<std::thread> threads(num_threads - 1);

аппаратный предел числа потоков (ядер)

макс. число потоков

число потоков размер блока

24

Page 25: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Параллельная версия алгоритма accumulate

Iterator block_start = first;

for (unsigned long i = 0; i < num_threads - 1; i++) { Iterator block_end = block_start;

std::advance(block_end, block_size); // конец блока

// каждый поток рассчитывает свой блок threads[i] = std::thread( accumulate_block<Iterator, T, BinOperation>(), block_start, block_end, std::ref(results[i]), op);

block_start = block_end; }

accumulate_block<Iterator, T, BinOperation>() (block_start, last, results[num_threads - 1], op);

std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));

return std::accumulate(results.begin(), results.end(), init, op); }

последний блок (+остаток)

25

Page 26: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Параллельная версия алгоритма accumulate

int main() { std::vector<int> vec(SIZE);

for (auto &x: vec) x = rand() % 10;

std::cout << "acc: " << parallel_accumulate(vec.begin(), vec.end(), 0, std::plus<int>()) << std::endl;

return 0;}

26

Page 27: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Защита разделяемых данных между потоками и синхронизация

27

Page 28: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы в С++

std::list<int> mylist;std::mutex lock;

void add(int elem) { std::lock_guard<std::mutex> guard(lock); mylist.push_back(elem);}

bool find(int elem) { std::lock_guard<std::mutex> guard(lock); return std::find(mylist.begin(), mylist.end(), elem) != mylist.end();}

28

Page 29: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы в С++

std::list<int> mylist;std::mutex lock;

void add(int elem) { std::lock_guard<std::mutex> guard(lock); if (elem < 0) throw "error"; mylist.push_back(elem);}

bool find(int elem) { std::lock_guard<std::mutex> guard(lock); if (mylist.size() == 0) return false; return std::find(mylist.begin(), mylist.end(), elem) != mylist.end();}

29

Page 30: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы в С++

class wrapper {private: data_t data; // защищаемые данные std::mutex mut;public: template<typename Function> void proc_data(Function func) { std::lock_guard<std::mutex> lock(mut); func(data); }};

data_t *unprotected; // внешний указатель

void unsafe_func(data_t &protected) { unprotected = &protected; }

wrapper obj;obj.proc_data(unsafe_func); unprotected->do_something(); // незащищённый доступ к data

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

30

Page 31: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы в С++

class wrapper {private: data_t data; // защищаемые данные std::mutex mut;public: template<typename Function> void proc_data(Function func) { std::lock_guard<std::mutex> lock(mut); func(data); }};

data_t *unprotected; // внешний указатель

void unsafe_func(data_t &protected) { unprotected = &protected; }

wrapper obj;obj.proc_data(unsafe_func); unprotected->do_something(); // незащищённый доступ к data

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

Нельзя передавать указатели и ссылки на защищённые данные за пределы области видимости блокировки никаким образом.

31

Page 32: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Адаптация интерфейсов к многопоточности

template <...> class stack {public: // ...

bool empty() const;

size_t size() const;

T& top();

T const &top() const;

void push(T const&);

void push(T&&);

void pop();

void swap(stack&&);

};

32

Page 33: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Адаптация интерфейсов к многопоточности

template <...> class stack {public: // ...

bool empty() const;

size_t size() const;

T& top();

T const &top() const;

void push(T const&);

void push(T&&);

void pop();

void swap(stack&&);

};

33

Page 34: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Адаптация интерфейсов к многопоточности

template <...> class stack {public: // ...

bool empty() const;

size_t size() const;

T& top();

T const &top() const;

void push(T const&);

void push(T&&);

void pop();

void swap(stack&&);

};

некорректный результат как решить?

stack<int> s;

if (!s.empty()) {

int const value = s.top();

s.pop();

// ...

}

34

Page 35: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Адаптация интерфейсов к многопоточности

std::vector<int> result;mystack.pop(result);

1. Передавать ссылку в функцию pop

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

3. Возвращать указатель на вытолкнутый элемент

4. Одновременно 1 и один из вариантов 2 или 3

std::shared_ptr<T> pop()

35

Page 36: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

template<typename T>class safe_stack {private: std::stack<T> data; mutable std::mutex m;public: safe_stack(); safe_stack(const safe_stack&);

// стек нельзя присваивать safe_stack& operator=(const safe_stack&) = delete; void push(T new_value); std::shared_ptr<T> pop(); void pop(T& value); bool empty() const;

// swap отсутствует // -- интерфейс предельно упрощён --};

36

Page 37: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

safe_stack(const safe_stack &rhs) { std::lock_guard<std::mutex> lock(rhs.m); data = rhs.data;}

std::shared_ptr<T> pop() { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); // выделяем память под возвращаемое значение std::shared_ptr<T> const res(std::make_shared<T>(data.top())); data.pop(); return res;}

void pop(T& value) { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); value = data.top(); data.pop(); }

37

Page 38: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Дедлоки: захват нескольких мьютексов

class Widget {private: data obj; std::mutex m;public: Widget(data const &d): data(d) {} friend void swap(Widget &lhs, Widget &rhs) { if (&lhs == &rhs) return; std::lock(lhs.m, rhs.m); // захыватываем мьютекс

// adopt_lock: lock_a и lock_b начинают владеть // захваченной блокировкой std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock); std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock); swap(lhs.some_detail, rhs.some_detail); }};

lock - “всё или ничего”

38

Page 39: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Дедлоки: иерархические мьютексы

hierarchical_mutex(100) hierarchical_mutex(50) hierarchical_mutex(30)

1 -> 2 -> 3

1

2

3

2 -> 3 3 -> 1 3 -> 2

порядок запирания 39

Page 40: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Дедлоки: иерархические мьютексы

hierarchical_mutex(100) hierarchical_mutex(50) hierarchical_mutex(30)

1

2

3

порядок запирания

1 -> 2 -> 3 2 -> 3 3 -> 1 3 -> 2

40

Page 41: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Дедлоки: иерархические мьютексы - пример

hier_mutex high_level_mut(10000);hier_mutex low_level_mut(5000);

int low_level_func() { // низкоуровневая блокировка std::lock_guard<hier_mutex> lock(low_level_mut); do_low_level_stuff();}

void high_level_func() { // высокоуровневая блокировка std::lock_guard<hier_mut> lock(high_level_mut); do_high_level_stuff(low_level_func()); // корректный } // порядок

void thread_a() { high_level_func(); // всё ок}

hier_mutex lowest_level_mut(100);void thread_b() { // некорректный порядок блокировки! std::lock_guard<hier_mut> lock(lowest_level_mut); high_level_func(); // вызов недопустим} 41

Page 42: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Иерархические мьютексы - возможная реализация

class hier_mutex { // hierarchical mutexprivate:

std::mutex internal_mut; unsigned long const hier_val; // текущий уровень unsigned prev_hier_val; // предыдущий уровень

// уровень иерархии текущего потока static thread_local unsigned long this_thread_hier_val;

void check_for_hier_violation() { if (this_thread_hier_val <= hier_val) { throw std::logic_error("mutex hierarchy violated"); } }

// обновить текущий уровень иерархии потока void update_hier_val() { prev_hier_val = this_thread_hier_val; this_thread_hier_val = hier_val; }

42

Page 43: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Иерархические мьютексы - возможная реализация

public: explicit hier_mutex(unsigned long value): hier_val(value), prev_hier_value(0) {}

void lock() { check_for_hier_violation(); internal_mutex.lock(); update_hier_val(); }

void unlock() { this_thrad_hier_val = prev_hier_val; internal_mutex.unlock(); }

void trylock() { // ... }

thread_local unsigned long hier_mutex::this_thread_hier_val(ULONG_MAX);

43

Page 44: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Блокировка с помощью std::unique_lock

class Widget {

int val;

std::mutex m;

int getval() const {

return val; }

};

bool Cmp(Widget &lhs, Widget &rhs) { // не захватываем пока мьютексы std::unique_lock<std::mutex> lock1(lhs.m,std::defer_lock); std::unique_lock<std::mutex> lock2(rhs.m,std::defer_lock);

// а вот сейчас захватываем, причём без дедлоков std::lock(lock1, lock2); return lhs.getval() > rhs.getval() ? true : false;}

44

Page 45: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Блокировка с помощью std::unique_lock

class Widget {

int val;

std::mutex m;

int getval() const {

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

return val; }

};

bool Cmp(Widget &lhs, Widget &rhs) { // обе операции совершаются под защитой мьютекса int const lhs_val = lhs.getval(); int const rhs_val = rhs.getval(); std::lock(lock1, lock2);

return lhs_val > rhs_val ? true : false;}

Минимизация гранулярности блокировки!

45

Page 46: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Блокировка с помощью std::unique_lock

void pop_and_process() { std::unique_lock<std::mutex> lock(mut); Widget data = queue.pop(); // получить элемент данных lock.unlock(); // освободить мьютекс super_widget result = process(data); // обработать данные lock.lock(); // опять захватить мьютекс output_result(data, result); // вывести результат}

Минимизация блокировок!

▪ блокировать данные, а не операции▪ удерживать мьютекс столько, сколько необходимо

▫ тяжёлые операции (захват другого мьютекса, ввод/вывод и т.д.) - вне текущей критической секции

46

Page 47: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Однократный вызов и отложенная инициализация

class NetFacility {private: connect_handle connection; bool connection_flag; void open_connection() { connection = connect_manager.open(); }

public: NetFacility(connect_info &_info): {} void send_data(data_packet const &d) { // отложенная инициализация if (connection_flag == false) connection = open_connection(); connection.send(data); }

void recv_data() { /* ... */ }} А если несколько

потоков?47

Page 48: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Однократный вызов и отложенная инициализация

class NetFacility {private: connect_handle connection; bool connection_flag; std::mutex mut; void open_connection() { connection = connect_manager.open(); }

public: NetFacility(connect_info &_info): {} void send_data(data_packet const &d) { std::unique_lock<std::mutex> lock(mut); if (connection_flag == false) // только инициализация требует защиты! connection = open_connection(); mut.unlock(); connection.send(data); }

void recv_data() { /* ... */ } };

Защищать только инициализацию

48

Page 49: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Однократный вызов и отложенная инициализация

class NetFacility {private: connect_handle connection; bool connection_flag; std::mutex mut; void open_connection() { connection = connect_manager.open(); }

public: NetFacility(connect_info &_info): {} void send_data(data_packet const &d) { if (connection_flag == false) { // гонка! std::lock_guard<std::mutex> lock(mut); if (connection_flag == false) { connection = open_connection(); connection_flag = true; // гонка! } }

connection.send(data); }

двойная проверка

49

Page 50: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Однократный вызов и отложенная инициализация

class NetFacility {private: connect_handle connection; std::once_flag connection_flag; void open_connection() { connection = connect_manager.open(info); }

public: NetFacility(connect_info &_info): {} void send_data(data_packet const &d) { // вызывается только один раз std::call_once(connection_flag, &NetFacility::open_connection, this); connection.send(data); }

void recv_data() { /* ... */ }}

50

Page 51: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

R/W-мьютексы в С++

class Widget { mutable std::shared_timed_mutex mut; int data;public: Widget& operator=(const R& rhs) { // эксклюзивные права на запись в *this std::unique_lock<std::shared_timed_mutex> lhs(mut, std::defer_lock);

// разделяемые права на чтение rhs std::shared_lock<std::shared_timed_mutex> rhs(other.mut, std::defer_lock);

std::lock(lhs, rhs);

// выполнить присваивание data = rhs.data; return *this; }};

51

Page 52: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

R/W-мьютексы в С++

int Widget::read() { std::shared_lock<shared_timed_mutex> lock(mut); return val;}

void Widget::set_value(int _val) { std::lock_guard<shared_mutex> lock(mut); val = _val;}

52

Page 53: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Рекурсивные мьютексы

▪ std::recursive_mutex

▪ мьютекс можно запирать несколько раз в одном потоке

▪ освобождать мьютекс требуется столько раз, сколько он был захвачен

▪ использование - аналогично std::mutex (std::lock_guard, std::unique_lock, …)

53

Page 54: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Условные переменные

▪ std::condition_variable, std::condition_variable_any

условная переменная, необходимо взаимодействие с мьютексом (condition_variable) или с любым классом (condition_variable_any), подобным мьютексу

▪ wait - ожидание условия

▪ wait_for, wait_until - ожидание условия заданное время или до заданного момента

▪ notify_one - сообщить одному потоку

▪ notify_all - сообщить всем потокам

54

Page 55: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Условные переменные - производитель-потребитель

std::mutex mut;std::queue<Widget> widget_queue;std::condition_variable cond;

void producer() { for (;;) { Widget const w = get_request(); std::lock_guard<std::mutex> lock(mut); widget_queue.push(data); cond.notify_one();} }

void consumer() { for (;;) { std::unique_lock<std::mutex> lock(mut); cond.wait(lock, []{return !widget_queue.empty();}); Widget w = widget_queue.pop(); lock.unlock(); process(widget);} } 55

Page 56: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Будущие результаты

56

Page 57: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Будущие результаты (future)

std::future<> & std::shared_future<>57

Page 58: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Будущие результаты (future)

int thinking();

// Запуск асинхронной (“фоновой”) задачиstd::future<int> answer = std::async(thinking);

// Работа основного потокаdo_other_stuff(); // в этом время работает thinking()

// Получение результатовstd::cout << "The answer is " << answer.get() << std::endl;

T1main thread

работа ожидание

T2thinking...

answer.get()

async

58

Page 59: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Будущие результаты (future)

struct Widget { void foo(std::string const&, int); int bar(std::string const&); int operator()(int);};

Widget w;

// Вызывается foo("carpe dieum", 2014) для объекта wauto f1 = std::async(&Widget::foo, &w, "carpe diem", 2014);

// Вызывается bar("carpe dieum", 2014) для объекта tmp = wauto f2 = std::async(&Widget::bar, w, "carpe diem");

// Вызывается tmp.operator(2014), где tmp = wauto f3 = std::async(Widget(), 2014);

// Вызвается w(1234)auto f4 = std::async(std::ref(w), 2014);

59

Page 60: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Будущие результаты (future)

struct Widget { Widget(); Widget(Widget&&); // Конструктор перемещения Widget(Widget const&) = delete; // Запретить копирование

// Оператор “перемещающее присваивание” Widget& operator=(Widget&&);

// Запретить присваивание Widget& operator=(Widget const&) = delete;

void foo(std::string const&, int); int bar(std::string const&); int operator()(int);};

Widget w;auto f1 = std::async(&Widget::foo, &w, "hi", 2014);auto f2 = std::async(&Widget::bar, w, "hi");auto f3 = std::async(Widget(), 2014);auto f4 = std::async(std::ref(w), 2014);

60

Page 61: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Будущие результаты (future)

▪ std::launch::async - запуск функции в асинхронном режиме

▪ std::launch::deferred - запуск в момент вызова wait или get

▪ std::launch::async | std::launch::deferred - на усмотрение реализации (по умолчанию)

auto f5 = std::async(std::launch::deferred, Widget::foo(), "carpe diem", 2014);auto f6 = std::async(std::launch::deferred, Widget::bar(), "carpe diem");auto f7 = std::async(std::launch::async, Widget(), 2014);

std::cout << f5.get() << std::endl; // вызывается foo()f6.wait(); // вызывается bar()std::cout << f7.get() << std::endl; // только ожидание // результата

61

Page 62: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи

task 1 task 2 task 3

package 1 package 2 package 3

62

Page 63: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи

▪ Шаблон std::packaged_task<> связывается будущий результат (future) с функцией

▪ Вызов функции происходит при вызове объекта packaged_task

▪ Параметр шаблона - сигнатура функции

template<> class packaged_task<int(float, char)> {public: template<typename Callable> explicit packaged_task(Callable &func); std::future<int> get_future(); void operator()(std::vector<char>*, int);};

пример спецификации шаблона для сигнатуры функции int func(float, char)

63

Page 64: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи - пример (пул задач)

task

package

task

package

tasks.push_back( std::move(task));

std::packaged_task<void()> task = std::move(tasks.front());

batch_systemadd_task

64

Page 65: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи - пример

std::mutex mut;std::deque<std::packaged_task<void()>> tasks;bool exit_flag = false;

bool is_exit() { std::mutex mut; std::lock_guard<std::mutex> lock(mut); return exit_flag;}

void batch_system() { while (!is_exit()) { std::unique_lock<std::mutex> lock(mut); if (tasks.empty()) continue; std::packaged_task<void()> task = // получить упакованную std::move(tasks.front()); // задачу из очереди tasks.pop_front(); // удалить из очереди lock.unlock(); task(); // запуск задачи} } 65

Page 66: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи - пример

template<typename func>std::future<void> add_task(func f){ std::packaged_task<void()> task(f); std::future<void> res = task.get_future(); std::lock_guard<std::mutex> lock(mut); tasks.push_back(std::move(task));

return res;}

void say_vox() { std::cout << "vox\n"; }void say_populi() { std::cout << "populi\n"; }void say_dei() { std::cout << "dei\n"; }void write_word() { std::string s; std::cin >> s; }

66

Page 67: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи - пример

int main() { std::thread batch(batch_system);

add_task(say_vox); add_task(say_populi); add_task(write_word); add_task(say_vox); add_task(say_dei);

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

std::mutex mut; std::unique_lock<std::mutex> lock(mut); exit_flag = true; lock.unlock(); batch.join();

return 0;}

67

Page 68: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Упакованные задачи - пример, возможные варианты

$ ./prog voxpopuli

,

$ ./prog voxpopuli

,voxdei

$ ./prog voxpopuli

,vox

68

Page 69: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 1

void print_value(std::future<int>& fut) { int x = fut.get(); std::cout << "value: " << x << std::endl;}

int compute_value() { std::this_thread::sleep_for(std::chrono::seconds(1)); return 42;}

int main () { std::promise<int> prom;

// Получаем объект future из созданного promise (обещаем) std::future<int> fut = prom.get_future(); // Отправляем будущее значение в новый поток std::thread th1 (print_value, std::ref(fut));

int val = compute_value(); prom.set_value(val); // Выполняем обещание th1.join();} 69

Page 70: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 1

mainmain thread

th1print_value

prom.set_value()

print_value

th1(print_value, std::ref(fut))

fut.get()

compute_value

работа ожидание

создание/завершениепотоков синхронизация

70

Page 71: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Недостатки синхр-ции на основе условных переменных

std::mutex mut;std::queue<Widget> widget_queue;std::condition_variable cond;

void producer() { for (;;) { Widget const w = get_request(); std::lock_guard<std::mutex> lock(mut); widget_queue.push(data); cond.notify_one();} }

void consumer() { for (;;) { std::unique_lock<std::mutex> lock(mut); cond.wait(lock, []{return !widget_queue.empty();}); Widget w = widget_queue.pop(); lock.unlock(); process(widget);} }

71

Page 72: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Недостатки синхр-ции на основе условных переменных

▪ “Код с запашком” (code smell)Например, потоки выполняют код, который не нуждается в блокировке мьютекса (например, один поток инициализирует структуру, после чего сообщает другому, что структура готова).

▪ Пропущенный сигналПоток может отправить сигнал (notify_one/all) в тот момент, когда другой поток не начал ожидать

▪ Ложное пробуждение (spurious wakeup)Поток, ожидающий сигнала может проснуться тогда, когда сигнал не был отправлен (или когда он был отправлен не этому потоку, или когда условие перестало выполняться). Нужна дополнительная проверка! (return !widget_queue.empty();) А что, если он не может проверить?! 72

Page 73: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise)

std::futurestd::promise<...> p

I'm waiting...p.get_future().wait()

73

Page 74: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise)

std::futurestd::promise<...> p

I'm waiting...p.get_future().wait()

ok, let’s move!

p.set_value()

74

Page 75: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise)

std::futurestd::promise<...> p

I'm waiting...p.get_future().wait()

ok, let’s move!

p.set_value()

Можно отправить сигнал только один раз

75

Page 76: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 1 1/3

std::promise<void> p;

void react(); // реакция на условиеvoid detect() { // обнаружение условия std::thread t([] { p.get_future().wait() react(); });

// делаем что-то // в это время t спит p.set_value(); // разбудить t // делаем ещё что-то t.join();};

76

Page 77: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 1 1/3

std::promise<void> p;

void react(); // реакция на условиеvoid detect() { // обнаружение условия std::thread t([] { p.get_future().wait() react(); });

// а что, если здесь возникнет исключение?? p.set_value(); // разбудить t // делаем ещё что-то t.join();};

77

Page 78: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Множественная отправка сигналов или пример 1 2/3

std::promise<void> p;

void detect() {

auto sf = p.get_future().share();

std::vector<std::thread> vt;

for (int i = 0; i < threadsToRun; i++) { vt.emplace_back([sf]{ sf.wait(); react(); }); }

// ...

p.set_value();

// ...

for (auto &t: vt) t.join();};

78

Page 79: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 2

int main() { std::istringstream iss_numbers{"3 1 42 23 -23 93 2 -289"}; std::istringstream iss_letters{" a 23 b,e k k?a;si,ksa c"};

std::vector<int> numbers; std::vector<char> letters; std::promise<void> numbers_promise, letters_promise;

auto numbers_ready = numbers_promise.get_future(); auto letter_ready = letters_promise.get_future();

std::thread value_reader([&]{ std::copy(std::istream_iterator<int>{iss_numbers}, std::istream_iterator<int>{}, std::back_inserter(numbers));

numbers_promise.set_value();

std::copy_if(std::istreambuf_iterator<char>{iss_letters}, std::istreambuf_iterator<char>{}, std::back_inserter(letters), ::isalpha); letters_promise.set_value(); }); 79

Page 80: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 2

numbers_ready.wait(); // Ждать когда числа будут готовы

std::sort(numbers.begin(), numbers.end());

if (letter_ready.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout) { // выводим числа, пока обрабатываются символы for (int num : numbers) std::cout << num << ' '; std::cout << '\n'; numbers.clear(); // Числа уже были напечатаны }

letter_ready.wait(); std::sort(letters.begin(), letters.end());

for (char let : letters) std::cout << let << ' '; std::cout << '\n';

// If numbers were already printed, it does nothing. for (int num : numbers) std::cout << num << ' '; std::cout << '\n';

value_reader.join();}

80

Page 81: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise) - пример 2

mainmain

работа ожидание

value_reader

letters_promise.set_value()value_reader

fut.get()

iss_numbers iss_letters

number_ready.wait()

sort

letter_ready.wait_for

sort output

numbers_promise.set_value()

создание/завершениепотоков синхронизация

81

Page 82: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

“Обещанные” результаты (std::promise), варианты

a a a a b c e i k k k s s -289 -23 1 2 3 4 23 42 93 93

-289 -23 1 2 3 23 42 93 a a a b c e i k k k s s

82

Page 83: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Разделяемые будущие результаты shared_future

int main() { std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise; std::shared_future<void> ready_future(ready_promise.get_future()); std::chrono::time_point<std::chrono::high_resolution_clock> start;

auto fun1 = [&]() -> std::chrono::duration<double, std::milli> { t1_ready_promise.set_value(); ready_future.wait(); // ожидать сигнала из main() return std::chrono::high_resolution_clock::now() - start; };

auto fun2 = [&]() -> std::chrono::duration<double, std::milli> { t2_ready_promise.set_value(); ready_future.wait(); // ожидать сигнала из main() return std::chrono::high_resolution_clock::now() - start; }; 83

Page 84: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Разделяемые будущие результаты shared_future

auto result1 = std::async(std::launch::async, fun1); auto result2 = std::async(std::launch::async, fun2);

// ждать, пока потоки не будут готовы t1_ready_promise.get_future().wait(); t2_ready_promise.get_future().wait();

// потоки готовы - начать отчёт времени start = std::chrono::high_resolution_clock::now();

// запустить потоки ready_promise.set_value();

std::cout << "Thread 1 received the signal " << result1.get().count() << " ms after start\n" << "Thread 2 received the signal " << result2.get().count() << " ms after start\n";}

84

Page 85: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Разделяемые будущие результаты shared_future

main

работа ожидание

создание/завершениепотоков синхронизация

T1

T2t2_ready.set_value

start

return

return

output

ready.set_value

t1_ready.set_value

85

Page 86: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Быстрая сортировка в духе функционального прог-я

86

Page 87: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Быстрая сортировка в духе функционального прог-я

template<typename T>std::list<T> sequential_quick_sort(std::list<T> input) { if (input.empty()) { return input; } std::list<T> result; result.splice(result.begin(), input, input.begin()); T const &pivot = *result.begin();

auto divide_point = std::partition(input.begin(), input.end(), [&](T const& t){return t < pivot; });

std::list<T> lower_part; lower_part.splice(lower_part.end(), input, input.begin(), divide_point); auto new_lower( sequential_quick_sort(std::move(lower_part))); auto new_higher( sequential_quick_sort(std::move(input)));

result.splice(result.end(), new_higher); result.splice(result.begin(), new_lower); return result; } 87

Page 88: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Быстрая сортировка в духе функционального прог-я

lowerpart

input

input

pivot

splice

divide_point

new_lower

new_higher

88

Page 89: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Параллельный алгоритм быстрой сортировки

template<typename T>std::list<T> parallel_quick_sort(std::list<T> input) { if (input.empty()) { return input; } std::list<T> result; result.splice(result.begin(), input, input.begin()); T const &pivot = *result.begin();

auto divide_point = std::partition(input.begin(), input.end(), [&](T const& t){return t < pivot; });

std::list<T> lower_part; lower_part.splice(lower_part.end(), input, input.begin(), divide_point); std::future<std::list<T>> new_lower( std::async(parallel_quick_sort, std::move(lower_part))); auto new_higher( parallel_quick_sort(std::move(input)));

result.splice(result.end(), new_higher); result.splice(result.begin(), new_lower.get()); return result; } 89

Page 90: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

90

Page 91: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

auto func1() { std::cout << "begin thinking over the answer...\n"; std::this_thread::sleep_for(dur3); return 40;}

auto func2(int x) { std::cout << "continue thinking over the answer...\n"; std::this_thread::sleep_for(dur1); return x + 2;}

auto func3(int x) { std::cout << "still thinking...\n"; std::this_thread::sleep_for(dur2); return "number " + std::to_string(x);}

void do_some_stuff() { std::cout << "do some useful stuff"; }

void do_some_other_stuff() { std::cout << "do other stuff"; }91

Page 92: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

int main() { auto f1 = std::async(func1); auto f2 = std::async(func2, f1.get()); auto f3 = std::async(func3, f2.get());

std::cout << "waiting for the answer...\n";

do_some_stuff();

std::cout << "answer: " << f3.get() << std::endl;

do_some_other_stuff();

92

Page 93: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

int main() { auto f1 = std::async(func1); auto f2 = std::async(func2, f1.get()); auto f3 = std::async(func3, f2.get());

std::cout << "waiting for the answer...\n";

do_some_stuff();

std::cout << "answer: " << f3.get() << std::endl;

do_some_other_stuff();

Каждый раз после получения результата выполняется создание новой асинхронной задачи.

Поток может быть заблокирован при вызове get() для ожидания результата.

93

Page 94: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

$ ./prog begin thinking over the answer...continue thinking over the answer...waiting for the answer...do some useful stuffanswer: still thinking...number 42do some other useful stuff

94

Page 95: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

#define BOOST_THREAD_PROVIDES_FUTURE#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION#include <boost/thread/future.hpp>

int main() { auto f = boost::async([](){ return func1(); }).then([](auto f){ return func2(f.get()); }).then([](auto f){ return func3(f.get()); });

std::cout << "waiting for the answer...\n";

do_some_stuff();

f.then([](auto f){ std::cout << "answer: " << f.get() << std::endl; });

do_some_other_stuff(); 95

Page 96: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

#define BOOST_THREAD_PROVIDES_FUTURE#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION#include <boost/thread/future.hpp>

int main() { auto f = boost::async([](){ return func1(); }).then([](auto f){ return func2(f.get()); }).then([](auto f){ return func3(f.get()); });

std::cout << "waiting for the answer...\n";

do_some_stuff();

f.then([](auto f){ std::cout << "answer: " << f.get() << std::endl; });

do_some_other_stuff();

вызывающий поток не блокируется96

Page 97: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

#define BOOST_THREAD_PROVIDES_FUTURE#define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION#include <boost/thread/future.hpp>

int main() { auto f = boost::async([](){ return func1(); }).then([](auto f){ return func2(f.get()); }).then([](auto f){ return func3(f.get()); });

std::cout << "waiting for the answer...\n";

do_some_stuff();

f.then([](auto f){ std::cout << "answer: " << f.get() << std::endl; }).wait();

do_some_other_stuff();

вызывающий поток блокируется

97

Page 98: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

$ g++ -Wall -pedantic -pthread -lboost_system \ -lboost_thread -std=c++14 -O2 prog.cpp -o prog

$ ./progwaiting for the answer...do some useful stuffbegin thinking over the answer...continue thinking over the answer...still thinking...answer: number 42do some other useful stuff

98

Page 99: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Неблокирующие будущие результаты (then)

Блокирующие future Неблокирующие future

f2

f3

f1

f

▪ устанавливается явный порядок выполнения

▪ нет блокировок

▪ поток один

▪ порядок выполнения неопределён

▪ возможны блокировки

▪ для каждой задачи создаётся отдельный поток 99

Page 100: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения всех задач (when_all)

#define BOOST_THREAD_PROVIDES_FUTURE_WHEN_ALL_WHEN_ANY#include <boost/thread/future.hpp>

std::vector<boost::future<void>> task_chunk;

task_chunk.emplace_back(boost::async([]() { std::cout << "hello from task 1\n"; }));task_chunk.emplace_back(boost::async([]() { std::cout << "hello from task 2\n"; }));task_chunk.emplace_back(boost::async([]() { std::cout << "hello from task 3\n"; }));

auto join_task = boost::when_all(task_chunk.begin(), task_chunk.end());

do_some_stuff();

join_task.wait();

100

Page 101: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения всех задач (when_all)

f2

f1f3

Будущий результат f4 зависит от выполнения всех будущих результатов f1, f2, f3 и начинает выполняться после завершения выполнения задач, им соответствующих (подобно барьерной синхронизации).

f4

101

Page 102: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения всех задач (when_all)

std::vector<boost::future<int>> task_chunk;

task_chunk.emplace_back(boost::async(boost::launch::async, [](){ std::cout << "hello from task 1\n"; return 10; }));

task_chunk.emplace_back(boost::async(boost::launch::async, [](){ std::cout << "hello from task 2\n"; return 20; }));

task_chunk.emplace_back(boost::async(boost::launch::async, [](){ std::cout << "hello from task 3\n"; return 12; }));

auto join_task = boost::when_all(task_chunk.begin(), task_chunk.end()) .then([](auto results){ auto res = 0; for (auto &elem: results.get()) res += elem.get(); return res; });

do_some_stuff();

std::cout << "result: " << join_task.get() << std::endl;

join_task имеет тип

future< vector< future<T>>>

102

Page 103: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения всех задач (when_all)

f2

f1f3

f4

f5

103

Page 104: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения всех задач (when_all)

$ g++ -Wall -pedantic -pthread -lboost_system \ -lboost_thread -std=c++14 -O2 prog.cpp -o prog

$ ./prog hello from task 1hello from task 3hello from task 2do some useful stuffresult: 42

104

Page 105: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения какой-либо задачи (when_any)

std::vector<boost::future<decltype(M_PI)>> task_chunk;

task_chunk.emplace_back(boost::async(boost::launch::async, []() { std::this_thread::sleep_for(dur1); return M_PI; }));

task_chunk.emplace_back(boost::async(boost::launch::async, []() { std::this_thread::sleep_for(dur2); return M_E; }));

task_chunk.emplace_back(boost::async(boost::launch::async, []() { std::this_thread::sleep_for(dur3); return M_LN2; }));

auto join_task = boost::when_any(task_chunk.begin(), task_chunk.end()) .then([](auto results) { for (auto &elem: results.get()) { if (elem.is_ready()) { return elem.get(); } } exit(1); // this will never happen });

do_some_stuff();

std::cout << "result: " << join_task.get() << std::endl;105

Page 106: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения какой-либо задачи (when_any)

f2

f1f3

Будущий результат f4 зависит от выполнения одного из будущих результатов f1, f2, f3 и начинает выполняться после завершения выполнения хотя бы одной задачи (подобно синхронизации “эврика”).

f4

106

Page 107: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Ожидание выполнения какой-либо задачи (when_any)

$ g++ -Wall -pedantic -pthread -lboost_system \ -lboost_thread -std=c++14 -O2 prog.cpp -o prog

do some useful stuffresult: 2.71828

do some useful stuffresult: 0.693147

do some useful stuffresult: 3.14159

107

Page 108: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Реализация высокоуровневых средств синхронизации

108

Page 109: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы и очереди задач

class Logger {

std::fstream flog;

public:

void writelog(...) {

flog << current_time()

<< ":" << logmsg

<< std::endl;

}

};

class Logger {

std::fstream flog;

public:

void writelog(...) {

flog << current_time()

<< ":" << logmsg

<< std::endl;

}

};

Очереди задачБлокировка мьютекса

109

Page 110: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы и очереди задач

class Logger {

std::fstream flog;

std::mutex mut;

public:

void writelog(...) {

std::lock_guard

<std::mutex> lock(mut);

flog << current_time()

<< ":" << logmsg

<< std::endl;

}

};

class Logger {

std::fstream flog;

worker_thread worker;

public:

void writelog(...) {

worker.send([=]{

flog << current_time()

<< ":" << logmsg

<< std::endl;

});

}

};

Блокировка мьютекса Очереди задач

110

Page 111: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Мьютексы и очереди задач

Блокировка мьютекса

▪ потоки блокируются

▪ имеется возможность дедлока

▪ небольшая масштабируемость

▪ порядок следования сообщения в логе отличается от последовательности поступления

Очереди задач

▪ потоки не блокируются

▪ отсутствует возможность дедлока

▪ высокая масштабируемость

▪ порядок следования сообщения в логе совпадает с фактическим

111

Page 112: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Паттерн: потокобезопасная обёртка над данными

Требования к потокобезопасным “обёрткам”:

1. Сохранение интерфейсаwidget w; => w.func("hi folks!");wrapper<widget => w.func("hi folks!");

2. Универсальность. Заранее может быть неизвестны методы, которые необходимо обернуть. Некоторые методы сложно обернуть: конструкторы, операторы, шаблоны и т.д.

3. Поддержка транзакцийaccount.deposit(Sergey, 1000)account.withdraw(Ivan, 1000);log.print("user ", username, "data ");log.print("time ", logmsg);

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

112

Page 113: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Паттерн: обёртка над данными с блокировками

template<typename T>class wrapper {private: T t; // оборачиваемый объект ... // состояние враппера

public: monitor(T _t): t(_t) { }

template <typename F>

// 1. получаем любую функцию // 2. подставляем в неё оборачиваемый объект // 3. выполняем и возвращаем результат auto operator()(F f) -> decltype(f(t)) { // работа враппера auto ret = f(t); // ... return ret; }}; 113

Page 114: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными с блокировками

template<typename T>class monitor {private: T t; std::mutex m;

public: monitor(T _t): t(_t) { }

template <typename F> auto operator()(F f) -> decltype(f(t)) { std::lock_guard<std::mutex> lock(m);

// вызов “объявления” под защитой мьютекса return f(t); }};

114

Page 115: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными с блокировками

monitor<std::string> smon{"start "}; // инициализацияstd::vector<std::future<void>> v;

for (auto i = 0; i < 5; i++) { // выполнить асинхр. задачи... v.emplace_back(std::async(std::launch::async, [&, i]{

smon([=](auto &s){ // "объявление" функции s += "i = " + std::to_string(i); s += " "; });

smon([](auto &s){ // "объявление" функции std::cout << s << std::endl; }); }));}

for (auto &f: v) // дождаться завершения f.wait();

std::cout << "done\n";115

Page 116: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными с блокировками

start i = 1 start i = 1 i = 0 start i = 1 i = 0 i = 2 start i = 1 i = 0 i = 2 i = 4 start i = 1 i = 0 i = 2 i = 4 i = 3 done

start i = 0 start i = 0 i = 2 start i = 0 i = 2 i = 3 start i = 0 i = 2 i = 3 i = 1 start i = 0 i = 2 i = 3 i = 1 i = 4 done

start i = 0 start i = 0 i = 2 start i = 0 i = 2 i = 4 start i = 0 i = 2 i = 4 i = 1 start i = 0 i = 2 i = 4 i = 1 i = 3done 116

Page 117: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными с блокировками

monitor<std::ostream&> mon_cout{std::cout};std::vector<std::future<void>> v;

for (auto i = 0; i < 5; i++) { v.emplace_back(std::async(std::launch::async, [&, i]{ mon_cout([=](auto &cout){ cout << "i = " << std::to_string(i); cout << "\n"; }); mon_cout([=](auto &cout){ cout << "hi from " << i << std::endl; }); }));}

for (auto &f: v) f.wait();

mon_cout([](auto &cout){ cout << "done\n";}); 117

Page 118: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными с блокировками

i = 0i = 2hi from 2hi from 0i = 1hi from 1i = 3hi from 3i = 4hi from 4done

i = 0i = 3i = 2hi from 2i = 1hi from 1hi from 3i = 4hi from 4hi from 0done

i = 0hi from 0i = 4hi from 4i = 2hi from 2i = 3hi from 3i = 1hi from 1done

i = 0hi from 0i = 2hi from 2i = 3hi from 3i = 1hi from 1i = 4hi from 4done

118

Page 119: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

template<typename T> class concurrent {private: T t; concurrent_queue<std::function<void()>> q; bool done = false; std::thread thd;

public: concurrent(T t_): t{t_}, thd{[=]{ while (!done) { (*q.wait_and_pop())(); } } } { }

~concurrent() { q.push([=]{ done = true; }); thd.join(); }

template<typename F> void operator()(F f) { q.push([=]{ f(t); }); }}; 119

Page 120: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

concurrent<std::string> smon{"start "}; // инициализацияstd::vector<std::future<void>> v;

for (auto i = 0; i < 5; i++) { // выполнить асинхр. задачи... v.emplace_back(std::async(std::launch::async, [&, i]{

smon([=](auto &s){ // "объявление" функции s += "i = " + std::to_string(i); s += " "; });

smon([](auto &s){ // "объявление" функции std::cout << s << std::endl; }); }));}

for (auto &f: v) // дождаться завершения f.wait();

std::cout << "done\n";120

Page 121: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

start i = 0 start i = 0 i = 2 start i = 0 i = 2 i = 3 start i = 0 i = 2 i = 3 i = 1 start i = 0 i = 2 i = 3 i = 1 i = 4 done

start i = 0 donestart i = 0 i = 2 start i = 0 i = 2 i = 1 start i = 0 i = 2 i = 1 i = 3 start i = 0 i = 2 i = 1 i = 3 i = 4

start i = 0 start i = 0 i = 1 start i = 0 i = 1 i = 4 start i = 0 i = 1 i = 4 i = 3 start i = 0 i = 1 i = 4 i = 3 i = 2 done 121

Page 122: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

concurrent<std::ostream&> mon_cout{std::cout};std::vector<std::future<void>> v;

for (auto i = 0; i < 5; i++) { v.emplace_back(std::async(std::launch::async, [&, i]{ mon_cout([=](auto &cout){ cout << "i = " << std::to_string(i); cout << "\n"; }); mon_cout([=](auto &cout){ cout << "hi from " << i << std::endl; }); }));}

for (auto &f: v) f.wait();

mon_cout([](auto &cout){ cout << "done\n";}); 122

Page 123: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

i = 0hi from 0i = 2hi from 2i = 3hi from 3i = 4hi from 4i = 1hi from 1done

i = 0hi from 0i = 2hi from 2i = 3hi from 3i = 4hi from 4i = 1hi from 1done

i = 0i = 1hi from 1hi from 0i = 2hi from 2i = 4hi from 4i = 3hi from 3done

i = 0hi from 0i = 2hi from 2i = 1i = 3hi from 3hi from 1i = 4hi from 4done

123

Page 124: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

template<typename F>auto operator()(F f) -> std::future<decltype(f(t))> {

// создаём объект promise (shared_ptr<promise>) auto p = std::make_shared<std::promise<decltype(f(t))>>();

// получаем из promise объект future auto ret = p->get_future();

q.push([=]{ // выполняем обещание уже внутри потока try { p->set_value(f(t)); } catch (...) { p->set_exception(std::current_exception()); } });

return ret;}

Данная версия operator() позволяет вернуть значение при вызове функции:

124

Page 125: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

template<typename F>auto operator()(F f) -> std::future<decltype(f(t))> { auto p = std::make_shared<std::promise<decltype(f(t))>>(); auto ret = p->get_future();

q.push([=]{ try { set_value(*p, f, t); } catch (...) { p->set_exception(std::current_exception()); } });

return ret;}

template<typename Fut, typename F, typename T1>void set_value(std::promise<Fut>& p, F& f, T1& t){ p.set_value(f(t)); }

template<typename F, typename T1>void set_value(std::promise<void>& p, F& f, T1& t){ f(t); p.set_value(); }

125

Page 126: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка над данными на основе очереди задач

template<typename F>auto operator()(F f) -> std::future<decltype(f(t))> { auto p = std::make_shared<std::promise<decltype(f(t))>>(); auto ret = p->get_future();

q.push([=]{ try { set_value(*p, f, t); } catch (...) { p->set_exception(std::current_exception()); } });

return ret;}

auto f = smon([](auto &s){ s += "done\n"; std::cout << s; return s;});

std::cout << "return value: " << f.get() << std::endl; 126

Page 127: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная обёртка на основе очереди задач - применение

class backgrounder {public: future<bool> save(std::string file) { c([=](data& d) { ... // каждая функция - в отдельной транзакции }); }

future<size_t> print(some& stuff) { c([=, &stuff](data& d) { ... // атомарный неделимый вывод }); }

private: struct data { /* ... */ } // данные concurrent<data> c; // обёртка для потокобезопасного}; // выполнения операций с данными

127

Page 128: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Разработка параллельных структур данных с блокировками

128

Page 129: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Цель разработки параллельных структур данных

▪ Обеспечить параллельный доступ ▪ Обеспечить безопасность доступа▪ Минимизировать взаимные исключения▪ Минимизировать сериализацию

129

Page 130: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Цель разработки параллельных структур данных

Задачи проектирования структур данных с блокировками:▪ Ни один поток не может увидеть состояние, в котором

инварианты нарушены▪ Предотвратить состояние гонки▪ Предусмотреть возникновение исключений▪ Минимизировать возможность взаимоблокировок

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

мьютексами▪ обеспечить разный уровень защиты▪ изменить структуру данных для расширения

возможностей распраллеливания 130

Page 131: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасный стек - потенциальные проблемы

Потенциальные проблемы безопасности реализации потокобезопасных структур:

1. Гонки данных

2. Взаимоблокировки

3. Безопасность относительно исключений

4. ...

131

Page 132: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

struct empty_stack: std::exception { };

template<typename T> class threadsafe_stack {private: std::stack<T> data; mutable std::mutex m;

public: threadsafe_stack() {} threadsafe_stack(const threadsafe_stack &other) { std::lock_guard<std::mutex> lock(other.m); data = other.data; }

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

void push(T new_value) { std::lock_guard<std::mutex> lock(m); data.push(std::move(new_value)); }

Защита данных

132

Page 133: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

T pop() { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); auto value = data.top(); data.pop(); return value; }

bool empty() const { std::lock_guard<std::mutex> lock(m); return data.empty(); }};

133

Page 134: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасный стек - тестовая программа

threadsafe_stack<int> stack;

void pusher(unsigned nelems) { for (unsigned i = 0; i < nelems; i++) { stack.push(i); }}

void printer() { try { for (;;) { int val; stack.pop(val); } } catch (empty_stack) { std::cout << "stack is empty!" << std::endl; }}

int main() { std::thread t1(pusher, 5), t2(pusher, 5); t1.join(); t2.join(); std::thread t3(printer); t3.join();} 134

Page 135: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасный стек - безопасность исключений

T pop() { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); auto value = data.top();

data.pop(); return value; }

[невозвратная] модификация контейнера

2

1

3

4

135

Page 136: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Версия pop, безопасная с точки зрения исключений

std::shared_ptr<T> pop() { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); std::shared_ptr<T> const res( std::make_shared<T>(std::move(data.top())));

data.pop(); return res; }

void pop(T& value) { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); value = std::move(data.top());

data.pop(); }

1

2

3

4

5

6

[невозвратная] модификация контейнера

[невозвратная] модификация контейнера

136

Page 137: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасный стек - взаимоблокировки

struct empty_stack: std::exception { };

template<typename T> class threadsafe_stack {private: std::stack<T> data; mutable std::mutex m;

public: threadsafe_stack() {} threadsafe_stack(const threadsafe_stack &other) { std::lock_guard<std::mutex> lock(other.m); data = other.data; }

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

void push(T new_value) { std::lock_guard<std::mutex> lock(m); data.push(std::move(new_value)); }

DEADLOCK ?137

Page 138: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасный стек - взаимоблокировки

std::shared_ptr<T> pop() { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); std::shared_ptr<T> const res( std::make_shared<T>(std::move(data.top()))); data.pop(); return res; }

void pop(T& value) { std::lock_guard<std::mutex> lock(m); if (data.empty()) throw empty_stack(); value = std::move(data.top()); data.pop(); } bool empty() const { std::lock_guard<std::mutex> lock(m); return data.empty(); }};

DEADLOCK ?

DEADLOCK ?

138

Page 139: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

threadsafe_stack<int> stack;

void pusher(unsigned nelems) { for (unsigned i = 0; i < nelems; i++) { stack.push(i); }}

void printer() { try { for (;;) { int val; stack.pop(val); } } catch (empty_stack) { std::cout << "stack is empty!" << std::endl; }}

int main() { std::thread t1(pusher, 5), t2(pusher, 5); t1.join(); t2.join(); std::thread t3(printer); t3.join();}

Потокобезопасный стек - тестовая программа

Недостатки реализации:

▪ Сериализация потоков приводит к снижению производительности: потоки простаивают и не совершают полезной работы

▪ Нет средств, позволяющих ожидать добавления элемента

139

Page 140: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

template<typename T> class threadsafe_queue {private: mutable std::mutex mut; std::queue<T> data_queue; std::condition_variable data_cond;

public: threadsafe_queue() {}

void push(T new_value) { std::lock_guard<std::mutex> lk(mut); data_queue.push(std::move(new_value)); data_cond.notify_one(); } std::shared_ptr<T> wait_and_pop() { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{return !data_queue.empty();}); std::shared_ptr<T> res( std::make_shared<T>(std::move(data_queue.front()))); data_queue.pop(); return res; }

Потокобезопасная очередь с ожиданием

140

Page 141: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с ожиданием

void wait_and_pop(T &value) { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{return !data_queue.empty();}); value = std::move(data_queue.front()); data_queue.pop(); }

bool try_pop(T& value) { std::lock_guard<std::mutex> lk(mut); if (data_queue.empty()) return false; value = std::move(data_queue.front()); data_queue.pop(); return true; }

std::shared_ptr<T> try_pop() { // ... }

bool empty() const { /* ... */ }

141

Page 142: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь - тестовая программа

threadsafe_queue<int> queue;

void pusher(unsigned nelems) { for (unsigned i = 0; i < nelems; i++) { queue.push(i); }}

void poper(unsigned nelems) { for (unsigned i = 0; i < nelems; i++) { int val; queue.wait_and_pop(val); }}

int main() { std::thread t1(pusher, 5), t2(pusher, 5), t3(poper, 9);

t1.join(); t2.join(); t3.join();}

Не требуется проверка empty()

142

Page 143: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с ожиданием

void wait_and_pop(T &value) { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{return !data_queue.empty();}); value = std::move(data_queue.front()); data_queue.pop(); }

bool try_pop(T& value) { std::lock_guard<std::mutex> lk(mut); if (data_queue.empty()) return false; value = std::move(data_queue.front()); data_queue.pop(); return true; }

std::shared_ptr<T> try_pop() { // ... }

bool empty() const { /* ... */ }

Не вызывается исключение

143

Page 144: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

template<typename T> class threadsafe_queue {private: mutable std::mutex mut; std::queue<T> data_queue; std::condition_variable data_cond;public: threadsafe_queue() {}

void push(T new_value) { std::lock_guard<std::mutex> lk(mut); data_queue.push(std::move(new_value)); data_cond.notify_one(); } std::shared_ptr<T> wait_and_pop() { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{return !data_queue.empty();}); std::shared_ptr<T> res( std::make_shared<T>(std::move(data_queue.front()))); data_queue.pop(); return res; }

Очередь с ожиданием - безопасность исключений

При срабатывании исключения

в wait_and_pop (в ходе инициализации res)

другие потоки не будут разбужены

144

Page 145: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь - модифицированная версия

template<typename T> class threadsafe_queue {private: mutable std::mutex mut; std::queue<std::shared_ptr<T>> data_queue; std::condition_variable data_cond;

public: void push(T new_value) { std::shared_ptr<T> data( std::make_shared<T>(std::move(new_value))); std::lock_guard<std::mutex> lk(mut); data_queue.push(std::move(new_value)); data_cond.notify_one(); } std::shared_ptr<T> wait_and_pop() { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{ return !data_queue.empty(); }); std::shared_ptr<T> res = data_queue.front(); data_queue.pop(); return res; }

Очередь теперь хранит элементы

shared_ptr

Инициализация объекта теперь

выполняется не под защитой блокировки (и это весьма хорошо)

Объект извлекается напрямую 145

Page 146: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

void wait_and_pop(T &value) { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{return !data_queue.empty();}); value = std::move(*data_queue.front()); data_queue.pop(); }

bool try_pop(T& value) { std::lock_guard<std::mutex> lk(mut); if (data_queue.empty()) return false; value = std::move(*data_queue.front()); data_queue.pop(); return true; }

std::shared_ptr<T> try_pop() { // ... }

bool empty() const { /* ... */ }

Потокобезопасная очередь - модифицированная версия

Объект извлекается из

очереди напрямую, shared_ptr не

инициализируется- исключение не возбуждается!

146

Page 147: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь - модифицированная версия

std::shared_ptr<T> wait_and_pop() { std::unique_lock<std::mutex> lk(mut); data_cond.wait(lk, [this]{ return !data_queue.empty(); }); std::shared_ptr<T> res = data_queue.front(); data_queue.pop(); return res; }

bool try_pop(T& value) { std::lock_guard<std::mutex> lk(mut); if (data_queue.empty()) return false; value = std::move(*data_queue.front()); data_queue.pop(); return true; }

std::shared_ptr<T> try_pop() { // ... }

bool empty() const { /* ... */ }

Объект извлекается из очереди напрямую,

shared_ptr не инициализируется

Недостатки реализации:

▪ Сериализация потоков приводит к снижению производительности: потоки простаивают и не совершают полезной работы

147

Page 148: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

Head Tail

148

Page 149: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

template<typename T> class queue {private: struct node { T data; std::unique_ptr<node> next; node(T _data): data(std::move(_data)) {} }; std::unique_ptr<node> head; node* tail;

public: queue() {} queue(const queue &other) = delete; queue& operator=(const queue &other) = delete;

Использование unique_ptr<node>

гарантирует удаление узлов без

использования delete

149

Page 150: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

std::shared_ptr<T> try_pop() { if (!head) { return std::shared_ptr<T>(); } std::shared_ptr<T> const res( std::make_shared<T>(std::move(head->data))); std::unique_ptr<node> const old_head = std::move(head); head = std::move(old_head->next); return res; }

void push(T new_value) { std::unique_ptr<node> p(new node(std::move(new_value))); node* const new_tail = p.get(); if (tail) tail->next = std::move(p); else head = std::move(p); tail = new_tail; } }; 150

Page 151: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

std::shared_ptr<T> try_pop() { if (!head) { return std::shared_ptr<T>(); } std::shared_ptr<T> const res( std::make_shared<T>(std::move(head->data))); std::unique_ptr<node> const old_head = std::move(head); head = std::move(old_head->next); return res; }

void push(T new_value) { std::unique_ptr<node> p(new node(std::move(new_value))); node* const new_tail = p.get(); if (tail) tail->next = std::move(p); else head = std::move(p); tail = new_tail; } };

push изменяет как tail, так и

head

необходимо будет защищать оба одновременно 151

Page 152: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

std::shared_ptr<T> try_pop() { if (!head) { return std::shared_ptr<T>(); } std::shared_ptr<T> const res( std::make_shared<T>(std::move(head->data))); std::unique_ptr<node> const old_head = std::move(head); head = std::move(old_head->next); return res; }

void push(T new_value) { std::unique_ptr<node> p(new node(std::move(new_value))); node* const new_tail = p.get(); if (tail) tail->next = std::move(p); else head = std::move(p); tail = new_tail; } };

pop и push обращаются к head->next и tail->next

если в очереди 1 элемент, то head->next и tail->next -

один и тот же объект 152

Page 153: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

Head Tail

next next

153

Page 154: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Модифицированная версия

Head Tail

Фиктивный узел

▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail.

▪ При очереди с одним элементом head->next и tail->next указывают на разные узлы (причём head->next == tail), в результате чего гонки не возникает. 154

Page 155: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Пустая очередь

Head Tail

Фиктивный узел

▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail.

155

Page 156: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с одним элементом

Head Tail

Фиктивный узел

▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail.

▪ При очереди с одним элементом head->next и tail->next указывают на разные узлы (причём head->next == tail), в результате чего гонки не возникает. 156

Page 157: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

template<typename T>class queue {private: struct node { std::shared_ptr<T> data; std::unique_ptr<node> next; }; std::unique_ptr<node> head; node *tail;

public: queue(): head(new node), tail(head.get()) {} queue(const queue &other) = delete; queue &operator=(const queue &other) = delete;

node хранит указатель на данные

▪ Вводится фиктивный узел▪ При пустой очереди head и tail теперь

указывают на фиктивный узел, а не на NULL

указатель на данные вместо данных

создание первого фиктивного узла в конструкторе

157

Page 158: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

std::shared_ptr<T> try_pop() { if (head.get() == tail) { return std::shared_ptr<T>(); } std::shared_ptr<T> const res(head->data); std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return res; }

void push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value))); std::unique_ptr<node> p(new node); tail->data = new_data; node *const new_tail = p.get(); tail->next = std::move(p); tail = new_tail; }

head сравнивается с tail, а не с NULL

данные извлекаются непосредственно без конструирования

создание нового экземпляра T

создание нового фиктивного узла

записываем в старый фиктивный узел новое

значение 158

Page 159: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Добавление нового элемента в очередь (push)

tail next

data

159

Page 160: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Добавление нового элемента в очередь (push)

tail next

data

p(new node)

160

Page 161: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Добавление нового элемента в очередь (push)

tail next

data

tail->data = new_data

p(new node)

161

Page 162: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Добавление нового элемента в очередь (push)

tail next

data

new_tail

new_tail = p.get()

next

data

p(new node)

162

Page 163: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Добавление нового элемента в очередь (push)

tail next new_tail

tail->next = std::move(p)

data

next

data

163

Page 164: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Добавление нового элемента в очередь (push)

tail next

data

tail = new_tail

164

Page 165: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Очередь с мелкозернистыми блокировками

std::shared_ptr<T> try_pop() { if (head.get() == tail) { return std::shared_ptr<T>(); } std::shared_ptr<T> const res(head->data); std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return res; }

void push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value))); std::unique_ptr<node> p(new node); tail->data = new_data; node *const new_tail = p.get(); tail->next = std::move(p); tail = new_tail; }

обращение к tail только на момент

начального сравнения

push обращается

только к tail

try_pop обращается

только к head

165

Page 166: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

Head Tail

▪ Функция push обращается только к tail, try_pop - только к head (и tail на короткое время).

▪ Вместо единого глобального мьютекса можно завести два отдельных и удерживать блокировки при доступке к head и tail.

1 2

166

Page 167: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

template<typename T> class queue {private: struct node { std::shared_ptr<T> data; std::unique_ptr<node> next; };

std::mutex head_mutex, tail_mutex; std::unique_ptr<node> head; node *tail;

node *get_tail() { std::lock_guard<std::mutex> tail_lock(tail_mutex); return tail; }

std::unique_ptr<node> pop_head() { std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == get_tail()) return nullptr std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }

блокируется только на момент получения элемента tail

167

Page 168: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue &other) = delete; threadsafe_queue &operator=(const threadsafe_queue &other)=delete;

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

void push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value))); std::unique_ptr<node> p(new node); node* const new_tail = p.get(); std::lock_guard<std::mutex> tail_lock(tail_mutex); tail->data = new_data; tail->next = std::move(p); tail = new_tail; }};

push обращается только к tail, но не к head, поэтому используется одна блокировка

168

Page 169: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue &other) = delete; threadsafe_queue &operator=(const threadsafe_queue &other)=delete;

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

void push(T new_value) { node *const old_tail = get_tail(); std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == old_tail) { return nullptr; } std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }};

выполняется не под защитой мьютекса

head_mutex

169

Page 170: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

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

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue &other) = delete; threadsafe_queue &operator=(const threadsafe_queue &other)=delete;

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

void push(T new_value) { node *const old_tail = get_tail(); std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == old_tail) { return nullptr; } std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }};

выполняется не под защитой мьютекса

head_mutex

170

Page 171: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента

Особенности реализации:

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

▪ Проверку условия можно выполнять под защитой head_mutex, захватывая tail_mutex только для чтения tail. Предикат выглядит как head != get_tail()

▪ Для версии pop, работающей со ссылкой, необходимо переопределить wait_and_pop(), чтобы обеспечить безопасность с точки зрения исключений. Необходимо сначала скопировать данные из узла, а потом удалять узел из списка.

171

Page 172: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - объявление класса

template<typename T> class queue {private: struct node { std::shared_ptr<T> data; std::uniquet_ptr<node> next; }; std::mutex head_mutex, tail_mutex; std::unique_ptr<node> head; node *tail; std::condition_variable data_cond;

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue& other) = delete; std::shared_ptr<T> try_pop(); bool try_pop(T& value); std::shared_ptr<T> wait_and_pop(); void wait_and_pop(T& value); void push(T new_value); void empty(); };

172

Page 173: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - добавление новых значений

template<typename T>void threadsafe_queue<T>::push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value)));

std::unique_ptr<node> p(new node); { std::lock_guard<std::mutex> tail_lock(tail_mutex); tail->data = new_data; node* const new_tail = p.get(); tail->next = std::move(p); tail = new_tail; }

data_cond.notify_one();}

173

Page 174: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - ожидение и извлечение элемента

template<typename T> class threadsafe_queue {

private: node* get_tail() { std::lock_guard<std::mutex> tail_lock(tail_mutex); return tail; }

std::unique_ptr<node> pop_head() { std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }

std::unique_lock<std::mutex> wait_for_data() { std::unique_lock<std::mutex> head_lock(head_mutex); data_cond.wait(head_lock, [&]{return head.get() != get_tail(); }); return std::move(head_lock); }

Модификация списка в результате удаления головного элемента.

Ожидание появления данных в очередиВозврат объекта блокировки 174

Page 175: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - ожидение и извлечение элемента

std::unique_ptr<node> wait_pop_head() { std::unique_lock<std::mutex> head_lock(wait_for_data()); return pop_head(); }

std::unique_ptr<node> wait_pop_head(T& value) { std::unique_lock<std::mutex> head_lock(wait_for_data()); value = std::move(*head->data); return pop_head(); }

public: std::shared_ptr<T> wait_and_pop() { std::unique_ptr<node> const old_head = wait_pop_head(); return old_head->data; }

void wait_and_pop(T& value) { std::unique_ptr<node> const old_head = wait_pop_head(value); }};

Модификация данных под защитой мьютекса, захваченного в wait_for_data

Модификация данных под защитой мьютекса, захваченного в wait_for_data

175

Page 176: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - try_pop() и empty()

private:

std::unique_ptr<node> try_pop_head() { std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == get_tail()) { return std::unique_ptr<node>(); } return pop_head(); }

std::unique_ptr<node> try_pop_head(T& value) { std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == get_tail()) { return std::unique_ptr<node>(); } value = std::move(*head->data); return pop_head(); }

176

Page 177: ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Работа с потоками. Защита данных

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - try_pop() и empty()

public:

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = try_pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

bool try_pop(T& value) { std::unique_ptr<node> const old_head = try_pop_head(value); return old_head; }

void empty() { std::lock_guard<std::mutex> head_lock(head_mutex); return (head.get() == get_tail()); }};

177