SlideShare ist ein Scribd-Unternehmen logo
1 von 176
Downloaden Sie, um offline zu lesen
Лекция 5. Многопоточное
программирование в языке С++.
Работа с потоками. Защита данных.
Синхронизация. Будущие результаты
Пазников Алексей Александрович
Кафедра вычислительных систем СибГУТИ
Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/
Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring
Параллельные вычислительные технологии
Весна 2015 (Parallel Computing Technologies, PCT 15)
Создание и завершение
работы потоков
2
О дивный новый [параллельный] мир!
#include <iostream>
#include <thread>
void hello() { // функция, которая реализует поток
std::cout << "hello brave new world!n";
}
int main() {
std::thread t(hello); // создаём поток
t.join(); // дожидаемся завершения
}
3
Запуск потока
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
Запуск потока
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 worldn";
});
thr.join();
“Most vexing parse”
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
Ожидание завершения потока в случае исключения
std::thread thr(myfunc);
try {
std::cout << "hello";
// ...
throw "error";
}
catch (...) {
thr.join(); // не забыть дождаться завершения
std::cout << "exception catchedn";
return 1;
}
thr.join();
7
RAII - передача ресурса есть инициализация
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);
do_some_work();
} // t.join()
8
Запуск нескольких потоков и ожидание завершения
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
Запуск нескольких потоков и идентификаторы потоков
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
Передача аргументов функции потока
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
Передача аргументов функции потока
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
Передача аргументов функции потока
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
Передача аргументов функции потока
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
Передача аргументов функции потока
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
Передача аргументов функции потока по ссылке
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
Передача управления потоком
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
Передача аргументов
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
Передача управления потоком
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
Передача управления потоком
std::thread foo() {
std::thread thr([](){ std::cout << "threadn"; });
return thr; // перемещение
}
void bar(std::thread thr) { thr.join(); }
int main() {
std::thread thr = foo();
bar(std::move(thr)); // перемещение
return 0;
}
20
RAII - передача ресурса есть инициализация
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))};
} // ~scoped_thread()
21
RAII - передача ресурса есть инициализация
try {
scoped_thread t{std::thread(func((local))};
// ...
if (cond)
throw "error"; // ~scoped_thread()
}
catch (...) {
std::cout << "exception catchedn";
return 1;
}
22
Параллельная версия алгоритма accumulate
Поток 1 Поток 2
Поток
num_threads
CPU CPU CPU
accumulate_block accumulate_block accumulate_block
23
Параллельная версия алгоритма 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);
}
};
24
Параллельная версия алгоритма 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);
аппаратный
предел числа
потоков (ядер)
макс. число
потоков
число потоков размер блока
25
Параллельная версия алгоритма 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); }
последний блок
(+остаток)
26
Параллельная версия алгоритма 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;
}
27
Защита разделяемых
данных между
потоками
и синхронизация
28
Мьютексы в С++
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();
}
29
Мьютексы в С++
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();
}
30
Мьютексы в С++
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
Мьютексы в С++
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
Любой код,
имеющий
доступ к
указателю или
ссылке, может
делать с ним
всё, что угодно,
не захватывая
мьютекс.
Нельзя передавать указатели и
ссылки на защищённые данные
за пределы области видимости
блокировки никаким образом.
32
Адаптация интерфейсов к многопоточности
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
Адаптация интерфейсов к многопоточности
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&&);
};
34
Адаптация интерфейсов к многопоточности
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();
// ...
}
35
Адаптация интерфейсов к многопоточности
std::vector<int> result;
mystack.pop(result);
1. Передавать ссылку в функцию pop
2. Потребовать наличия копирующего или перемещающего
конструктора, не возбуждающего исключений (доказано, что
можно объединить pop и top, но это можно сделать только если
конструкторы не вызывают исключений)
3. Возвращать указатель на вытолкнутый элемент
4. Одновременно 1 и один из вариантов 2 или 3
std::shared_ptr<T> pop()
36
Потокобезопасный стек
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 отсутствует
// -- интерфейс предельно упрощён --
};
37
Потокобезопасный стек
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();
}
38
Дедлоки: захват нескольких мьютексов
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 - “всё или ничего”
39
Дедлоки: иерархические мьютексы
hierarchical_mutex(100) hierarchical_mutex(50) hierarchical_mutex(30)
1 -> 2 -> 3
1
2
3
2 -> 3 3 -> 1 3 -> 2
порядок запирания 40
Дедлоки: иерархические мьютексы
hierarchical_mutex(100) hierarchical_mutex(50) hierarchical_mutex(30)
1
2
3
порядок запирания
1 -> 2 -> 3 2 -> 3 3 -> 1 3 -> 2
41
Дедлоки: иерархические мьютексы - пример
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(); // вызов недопустим
} 42
Иерархические мьютексы - возможная реализация
class hier_mutex { // hierarchical mutex
private:
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;
}
43
Иерархические мьютексы - возможная реализация
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);
44
Блокировка с помощью 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;
}
45
Блокировка с помощью 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;
}
Минимизация
гранулярности
блокировки!
46
Блокировка с помощью 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); // вывести результат
}
Минимизация блокировок!
▪ блокировать данные, а не операции
▪ удерживать мьютекс столько, сколько необходимо
▫ тяжёлые операции (захват другого мьютекса,
ввод/вывод и т.д.) - вне текущей критической секции
47
Однократный вызов и отложенная инициализация
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() { /* ... */ }
} А если несколько
потоков?
48
Однократный вызов и отложенная инициализация
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() { /* ... */ } };
Защищать только
инициализацию
49
Однократный вызов и отложенная инициализация
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);
}
двойная проверка
50
Однократный вызов и отложенная инициализация
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() { /* ... */ }
}
51
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;
}
};
52
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;
}
53
Рекурсивные мьютексы
▪ std::recursive_mutex
▪ мьютекс можно запирать несколько раз в
одном потоке
▪ освобождать мьютекс требуется столько
раз, сколько он был захвачен
▪ использование - аналогично std::mutex
(std::lock_guard, std::unique_lock,
…)
54
Условные переменные
▪ std::condition_variable, std::
condition_variable_any
условная переменная, необходимо взаимодействие с
мьютексом (condition_variable) или с любым
классом (condition_variable_any), подобным
мьютексу
▪ wait - ожидание условия
▪ wait_for, wait_until - ожидание условия заданное
время или до заданного момента
▪ notify_one - сообщить одному потоку
▪ notify_all - сообщить всем потокам
55
Условные переменные - производитель-потребитель
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);
} } 56
Будущие результаты
57
Будущие результаты (future)
std::future<> & std::shared_future<>
58
Будущие результаты (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
59
Будущие результаты (future)
struct Widget {
void foo(std::string const&, int);
int bar(std::string const&);
int operator()(int);
};
Widget w;
// Вызывается foo("carpe dieum", 2014) для объекта w
auto f1 = std::async(&Widget::foo, &w, "carpe diem", 2014);
// Вызывается bar("carpe dieum", 2014) для объекта tmp = w
auto f2 = std::async(&Widget::bar, w, "carpe diem");
// Вызывается tmp.operator(2014), где tmp = w
auto f3 = std::async(Widget(), 2014);
// Вызвается w(1234)
auto f4 = std::async(std::ref(w), 2014);
60
Будущие результаты (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);
61
Будущие результаты (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; // только ожидание
// результата 62
Упакованные задачи
task 1 task 2 task 3
package 1 package 2 package 3
63
Упакованные задачи
▪ Шаблон 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)
64
Упакованные задачи - пример (пул задач)
task
package
task
package
tasks.push_back(
std::move(task));
std::packaged_task<void()> task
= std::move(tasks.front());
batch_systemadd_task
65
task()
Упакованные задачи - пример
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(); // запуск задачи
} } 66
Упакованные задачи - пример
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 << "voxn"; }
void say_populi() { std::cout << "populin"; }
void say_dei() { std::cout << "dein"; }
void write_word() { std::string s; std::cin >> s; }
67
Упакованные задачи - пример
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;
}
68
Упакованные задачи - пример, возможные варианты
$ ./prog
vox
populi
,
$ ./prog
vox
populi
,
vox
dei
$ ./prog
vox
populi
,
vox
69
“Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
I'm waiting...p.get_future().wait()
70
“Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
Msg received!p.get_future().wait()
ok, let’s move!
p.set_value(msg)
71
“Обещанные” результаты (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();
} 72
“Обещанные” результаты (std::promise) - пример 1
mainmain thread
th1print_value
prom.set_value()
print_value
th1(print_value,
std::ref(fut))
fut.get()
compute_value
работа ожидание
создание/завершение
потоков
синхронизация
73
“Обещанные” результаты (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();
}); 74
“Обещанные” результаты (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();
}
75
“Обещанные” результаты (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()
создание/завершение
потоков
синхронизация
76
“Обещанные” результаты (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
77
Полезные советы
78
Используйте задачи,
а не потоки
79
Проблемы с параллелизмом на основе потоков
int doWork();
std::thread t(doWork); // 1
// или
auto fut = std::async(doWork); // 2
80
▪ Вариант, основанный на задаче (2), предпочтительней,
т.к. предполагает возвращаемое значение, которое
можно получить fut.get().
▪ Если doWork выбрасывает исключение, то get()
позволяет обработать исключения, в то время как в
первом случае выброс исключения приведёт к
завершению программы.
Проблемы с параллелизмом на основе потоков
81
Параллелизм задач находится на более высоком уровне
абстракции по сравнению с параллелизмом потоков,
освобождает программиста от деталей реализации:
▪ Аппаратные потоки (software threads) - те, которые
действительно выполняют вычисления (по числу ядер).
▪ Программные потоки (hardware threads) - потоки,
которые планируются ОС и выполняются на аппаратных
потоках.
▫ Легковесные потоки (lightweight threads) - потоки,
которые выполняются целиком в пространстве
пользователя.
▪ std::thread - объекты С++, которые соответствуют
определённым программным потокам, с которыми
можно выполнять операции join и detach
Ограниченность количества программных потоков
82
Программные потоки - ограниченный ресурс. Попытка
создать больше заданного числа потоков вызовет
исключение, даже если
int doWork() noexcept;
std::thread t(doWork); // может быть исключение!
▪ Запустить doWork в текущем потоке?
▪ Или подождать, пока освободится программный поток?
:(
Перегруженность аппаратных потоков (oversubscription)
83
Состояние перегруженности аппаратных потоков
oversubscription возникает, когда в системе большое
количество runnable-потоков.
Планировщик ОС выделяет программным потокам порции
(time-slice) процессорного времени.
После окончания порции происходит переключение
контекста (context switch), особенно в случае, когда поток
назначается на разные ядра:
▪ Кэш-память не загружена, большое количество
промахов по кэшу.
▪ Запуск нового потока на ядре перезаписывает записи
для старого потока, который, вероятно, будет опять
назначен на это ядро. Это опять приводит к промахам
по кэшу.
Перегруженность аппаратных потоков (oversubscription)
84
Выбор оптимального количества потоков для избежания
перегруженности зависит от:
▪ Момента, когда программа переходит из региона с
вводом-выводом к области с вычислениями.
▪ Стоимости переключения контекста
▪ Того, насколько эффективно потоки используют кэш
▪ Аппаратной архитектуры
Перегруженность аппаратных потоков (oversubscription)
85
Выбор оптимального количества потоков для избежания
перегруженности зависит от:
▪ Момента, когда программа переходит из региона с
вводом-выводом к области с вычислениями.
▪ Стоимости переключения контекста
▪ Того, насколько эффективно потоки используют кэш
▪ Аппаратной архитектуры
Сделай жизнь легче, используй std::async!
Ответственность за управление потоками
лежит на плечах разработчика стандартной
библиотеки!
Long live std::async!
86
std::async позволяет создать неограниченное количество
асинхронных функций
▪ Вызов std::async не гарантирует создание нового
программного потока (политики async и deferred).
▪ Асинхронная функция может быть запущена, например,
в том же потоке, где и вызывается get, wait,позволяя
избежать перегруженности (oversubscription).
▪ Возможность work-stealing (легковесных потоков)
Long live std::async! But...
87
std::async позволяет создать неограниченное количество
асинхронных функций
▪ Вызов std::async не гарантирует создание нового
программного потока (политики async и deferred).
▪ Асинхронная функция может быть запущена, например,
в том же потоке, где и вызывается get, wait,позволяя
избежать перегруженности (oversubscription).
▪ Возможность work-stealing (легковесных потоков)
Но std::async не универсальное средство. Недостатки:
▪ Приводит к возможному дисбалансу загрузки.
Планирование происходит на двух уровнях: ОС и
программы.
▪ Не подходит для некоторых целей (напр., GUI)
Когда таки нужно использовать потоки
88
▪ Нужно воспользоваться функционалом
низкоуровневой реализации потоков (например, std::
thread::native_handle)
▪ Нужно оптимизировать использвоание потоков в
программе. Допустим, если вы разрабатываете
сервер, который будет запускаться на заданной
архитектуре.
▪ Вы хотите реализовать потоки на архитектуре, где
пока нет реализации C++-concurrency.
Но это редко. В большинстве случаев смело
разрабатывайте программы на основе задач,
а не на основе потоков.
Быстрая сортировка в духе функционального прог-я
89
Быстрая сортировка в духе функционального прог-я
template<typename T>
std::list<T> sequential_quick_sort(std::list<T> 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; }
90
Быстрая сортировка в духе функционального прог-я
lower
part
input
input
pivot
splice
divide_point
new_lower
new_higher
91
Параллельный алгоритм быстрой сортировки
template<typename T>
std::list<T> parallel_quick_sort(std::list<T> 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; }
92
Учитывайте политику
запуска задачи
93
Помните про политику запуска задачи
94
▪ std::launch::async - функция будет запущена
асинхронного, т.е. в отдельном потоке
T1main thread
T2func
fut.get()
▪ std::launch::deferred - функция может быть
запущена только, когда вызан методв get или wait
для объекта future в потоке, вызывающем get (wait).
T1main thread
T2func
fut.get()
async
async
Помните про политику запуска задачи
95
▪ std::launch::async - функция будет запущена
асинхронного, т.е. в отдельном потоке
▪ std::launch::deferred - функция может быть
запущена только, когда вызан методв get или wait
для объекта future в потоке, вызывающем get (wait).
auto fut = std::async(func); // использовать политику
// запуска по умолчанию
▪ Нельзя предугадать, будет ли func выполняться
асинхронно
▪ Нельзя предугадать, будет ли func запущена на
потоке, отличном, от потока, вызывающего get (wait)
▪ Нельзя предугадать, что func будет выполнена.
Проблема: std::async и Thread Local Storage (TLS)
96
auto fut = std::async([](){ // Может использоваться
thread_local local_var; // TLS для независимого
... // потока
});
...
fut.get(); // а может и для этого!
Политика запуска по умолчанию конфликтует с
использованием переменных thread_local:
Проблема: std::async и цикла на основе wait_for
97
using namespace std::literals; // C++14 суффиксы
auto fut = std::async([]() {
std::this_thread::sleep_for(1s);
});
// Цикл, ожидающий выполнения std::async,
// может не завершиться
while (fut.wait_for(100ms) !=
std::future_status::ready)
{ /* делать что-то асинхронного */}
Использование циклов на основе вызова wait_for или
wait_until может привести к вечному ожиданию, если
задача будет запущена как отложенная (std::launch::
deferred):
Проблема: std::async и цикла на основе wait_for - решение
98
auto fut = std::async([]() {
std::this_thread::sleep_for(1s);
});
if (fut.wait_for(0s) ==
std::future_satus::deferred) {
fut.get(); // ожидаем результата
...
} else {
while (fut.wait_for(100ms) !=
std::future_status::ready) {
// делать какую-то работу асинхронного,
// пока ждём завершения выполнения задачи
}
// здесь fut готово
}
Когда использовать политику запуска по умолчанию
99
▪ Задача не требует асинхронного запуска в
отдельном потоке, отличном от вызывающего get
(wait).
▪ Не важно, thread_local-переменные какого потока
будут использоваться.
▪ Или есть гарантия, что get (wait) будут вызваны для
объекта future, возвращённого std::async, или
задача может быть вовсе на запущена.
▪ При использовании wait_for или wait_until
допускается возможность отложенного запуска
задачи.
Если какие-либо пункты не выполняются, лучше
гарантировать асинхронный запуск задачи через
передачу std::launch::async
Используйте потоки,
не требующие вызова
join (unjoinable)
100
Joinable и unjoinable
101
Объект std::thread может пребывать в двух состояниях:
▪ joinable: объект соответствует потоку, который
выполняется или может быть запущен.
▪ unjoinable: объект, с которым нельзя выполнить
операцию join:
▫ выполнен конструктур по умолчанию для std::
thread, т.е. std::thread может не имеет функции
для выполнения и поэтому
не соответствует реальному потоку.
▫ std::thread, который был перемещён (moved)
▫ std::thread, который был присоединён (joined)
▫ std::thread, который был отсоденинён (detached)
Проблема с joinable-потоками
102
constexpr auto n = 10'000'000; // C++14-style
bool doWork(std::function<bool(int)> pred, // условие
int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
std::thread t([&pred, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
... // низкоуровневые манипуляции с потоком
if (conditionAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
Проблема с joinable-потоками
103
constexpr auto n = 10'000'000; // C++14-style
bool doWork(std::function<bool(int)> pred, // условие
int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
std::thread t([&pred, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
...
if (conditionAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
sched_param sch;
int policy;
pthread_getschedparam(nh, &policy, &sch);
sch.sched_priority = 20;
if (pthread_setschedparam(nh, SCHED_FIFO, &sch)) {
std::cout << "Failed to setschedparam: "
<< std::strerror(errno) << 'n';
}
Проблема с joinable-потоками
constexpr auto n = 10'000'000; // C++14-style
bool doWork(std::function<bool(int)> pred, // условие
int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
std::thread t([&pred, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
});
auto nh = t.native_handle();
...
if (conditionAreSatisfied()) {
t.join();
performComputation(goodVals);
return true; // ok, т.к. был t.join()
}
return false; // не было t.join()! выброс исключения
} // и аварийное завершение программы 104
Проблема с joinable-потоками
Как можно решить проблему (наивно):
▪ Неявный join. Деструктор std::thread будет ожидать
завершения потока. Но это приведёт к неочевидному
коду, например, когда поток ждёт завершения doWork,
уже зная, что условие не выполнено.
▪ Неявный detach. Деструктор разрывает связь между
std::thread и потоком выполнения. Поток
продолжает работать. В этом случае, например, при
завершении функции doWork, поток продолжает
работать. Этот поток может использовать
автоматические переменные из стека doWork.
Поэтому стандарт запретил уничтожение потока в
сотоянии joinable: деструктур такого объекта вызывает
завершение программы.
105
Проблема с joinable-потоками
106
▪ Программист должен следить за тем, что объект
должен находиться в состоянии unjoinable вне его
области видимости.
▪ Обеспечение этого требования - задача непростая,
поскольку требует отслеживания всех выходов из
функции через return, continue, break, goto,
exception.
▪ Необходимо обеспечить выполнение определённого
действия каждый раз при выходе из блока.
Make it RAII!
▪ Программист должен следить за тем, что объект
должен находиться в состоянии unjoinable вне его
области видимости.
▪ Обеспечение этого требования - задача непростая,
поскольку требует отслеживания всех выходов из
функции через return, continue, break, goto,
exception.
▪ Необходимо обеспечить выполнение определённого
действия каждый раз при выходе из блока.
Решение: RAII-объекты (Resouce Acquisition Is
Initialization), (к которым относятся std::unique_ptr,
std::shared_ptr, std::lock_guard, std::fstream и
др.), деструктор которых содержит необходимое действие.
107
RAII
108
class ThreadRAII {
public:
enum class DestrAction { join, detach };
ThreadRAII(std::thread&& t, DestrAction a):
action{a}, t{std::move(t)} { }
~ThreadRAII() { // действие выполняется в деструкторе
if (t.joinable()) {
if (action == DestrAction::join) {
t.join();
} else {
t.detach();
}
}
}
std::thread& get() { return t; }
private:
DestrAction action; // action in destuctor
std::thread t;
};
RAII
109
bool doWork(std::function<bool(int)> pred, int maxVal = n) {
std::vector<int> goodVals; // значения, удовл. условию
ThreadRAII t{ // использовать RAII-объект
std::thread([&stencil, maxVals, &goodVals]{
for (auto i = 0; i <= maxVals; i++) {
if (pred(i)) goodVals.push_back(i); }
}), ThreadRAII::DestrAction::join // действие
}; // в деструкторе
auto nh = t.get().native_handle();
...
if (conditionAreSatisfied()) {
t.get().join();
performComputation(goodVals);
return true;
}
return false;
}
Используйте future-
promise для
синхронизации потоков
(а не cond vars)
110
“Обещанные” результаты (std::promise)
Msg received!
ok, let’s move!
cv.notify_one()
111
Условные
переменные?
std::unique_lock<std::mutex> lk(m);
cv.wait(lk)
cv
Недостатки синхр-ции на основе условных переменных
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);
} }
112
Недостатки синхр-ции на основе условных переменных
▪ Необходимость использования мьютекса
std::unique_lock<std::mutex> lock(mut);
cond.wait(lock, ...);
А что, если потоки выполняют код, который не нуждается в
блокировке мьютекса? Например, один поток инициализирует
структуру, после чего сообщает другому, что структура готова.
▪ Пропущенный сигнал
Поток может отправить сигнал (notify_one/all) тогда, когда другой
поток ещё не начал его ожидать.
▪ Ложное пробуждение (spurious wakeup)
Поток может проснуться тогда, когда сигнал не был отправлен Или
когда он был отправлен потока, а затем условие перестало
выполняться. Поэтому нужна дополнительная проверка:
cond.wait(lock, []{return !widget_queue.empty();}));
А что, если поток не может проверить условие?!
113
Недостатки синхр-ции на основе условных переменных
std::atomic<bool> flag(false);
...
flag = true;
...
while (!flag); // активное ожидание! :(
...
114
Для решения проблемы ложного пробуждения можно использовать
атомарный флаг:
Или так:
{
flag = true;
cv.notify_one();
}
{
cv.wait(lk, [] { return flag; }); // код “с запашком” :(
}
“Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
p.get_future().wait()
p.set_value()
115
ok, let’s move!
“Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
p.get_future().wait()
p.set_value()
116
ok, let’s move!
▪ Не требует мьютексов
▪ Не нуждается в атомарных флагах
▪ Не использует активного ожидания
▪ Не зависит от порядка выполнения wait, set_value
“Обещанные” результаты (std::promise)
std::futurestd::promise<...> p
p.get_future().wait()
p.set_value()
117
ok, let’s move!
▪ Не требует мьютексов
▪ Не нуждается в атомарных флагах
▪ Не использует активного ожидания
▪ Не зависит от порядка выполнения wait, set_value
▪ Надо заботиться о поведении деструктора future
▪ Можно отправить сигнал только один раз
“Обещанные” результаты (std::promise) - пример
std::promise<void> p;
void react(); // реакция на условие
void detect() { // обнаружение условия
std::thread t([] {
p.get_future().wait();
react();
});
// делаем что-то // в это время t спит
p.set_value(); // разбудить t
// делаем ещё что-то
t.join();
};
118
“Обещанные” результаты (std::promise) - пример
std::promise<void> p;
void react(); // реакция на условие
void detect() { // обнаружение условия
std::thread t([] {
p.get_future().wait();
react();
});
// а что, если здесь возникнет исключение??
p.set_value(); // разбудить t
// делаем ещё что-то
t.join();
};
119
“Обещанные” результаты (std::promise) - пример
std::promise<void> p;
void react(); // реакция на условие
void detect() { // обнаружение условия
ThreadRAII t(std::thread([] {
p.get_future().wait();
react();
)});
...
p.set_value(); // разбудить t
// делаем ещё что-то
t.join();
};
120
Множественная отправка сигналов
std::promise<void> p;
void detect() {
auto sf = p.get_future().share();
std::vector<std::thread> vt;
for (auto i = 0; i < threadsToRun; i++) {
vt.emplace_back([sf]{ sf.wait();
react(); });
}
// ...
p.set_value();
// ...
for (auto &t: vt) t.join();
};
121
Множественная отправка сигналов
std::promise<void> p;
void detect() {
auto sf = p.get_future().share();
std::vector<std::thread> vt;
for (auto i = 0; i < threadsToRun; i++) {
vt.emplace_back([sf]{ sf.wait();
react(); });
}
// ...
p.set_value();
// ...
for (auto &t: vt) t.join();
};
// RAII... 122
Множественная отправка сигналов
std::promise<void> p;
void detect() {
auto sf = p.get_future().share();
std::vector<ThreadRAII> vt;
for (auto i = 0; i < nthreads; i++) {
vt.emplace_back(std::move(ThreadRAII{
std::thread([sf]{
sf.wait();
react(); }
), ThreadRAII::DestrAction::join
}));
}
// ...
p.set_value();
};
123
Разделяемые будущие результаты 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;
}; 124
Разделяемые будущие результаты 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 startn"
<< "Thread 2 received the signal "
<< result2.get().count() << " ms after startn";
}
125
Разделяемые будущие результаты shared_future
main
работа ожидание
создание/завершение
потоков
синхронизация
T1
T2
t2_ready.
set_value
start
return
return
output
ready.
set_value
t1_ready.
set_value
126
Учитывайте поведение
деструктора объекта
future
127
Хранение результата для future
Деструктор объекта future ведёт себя иногда так, как
будто он выполняет неявный join, а в некоторых случае -
как будто выполняет неявный detach.
128
Вызываемый
поток
Вызывающий
поток
future std::promise
Где хранится результат вызывающего потока?
Вызывающий поток может завершиться до того, как
вызываемый выполнит fut.get(), и результат не может
храниться в объекте std::promise вызываемого потока.
Объект future не может быть хранилищем для результата,
т.к. он может быть скопирован в объекты shared_future,
после чего возникает вопрос, какая из копий соответствует
результату?
Два варианта поведения деструктора future
129
Вызываемый
поток
Вызывающий
поток
future std::promise
Результат
вызывающего
Поведение деструктора future зависит от разделяемого
состояния (shared state):
▪ Деструктор последнего объекта future,
указывающего на разделяемое состояние (shared
state) для какой-то асинхронной задачи, блокируется
до завершения выполнения этой задачи, т.е. выполняет
“join”.
▪ Деструкторы всех других объектов future просто
уничтожают объект future. Это аналогично вызову
detach для потока.
shared state
Два варианта поведения деструктора future
130
▪ Деструктор последнего объекта future, указывающего
на разделяемое состояние, выполняет “join”, если:
▫ объект указывает на разделяемое состояние,
созданное std::async
▫ задача, породившая future, была запущена
асинхронно
▫ future - это последний объект future, указывающий
на разделяемое состояние
Зачем это нужно?
▪ Чтобы избежать неявного вызова detach для потока, в
котором выполняется задача.
▪ Срабатывание деструктора не должно приводить к
завершению программы (попытка компромиса)
Два варианта поведения деструктора future
131
// Деструктор futs может блокироваться
std::vector<std::future<void>> futs;
// Объект может блокироваться при уничтожении
class Widget {
private:
std::shared_future<double> fut;
};
Два варианта поведения деструктора future - пример
132
auto fut1 = std::async(std::launch::async, [] {
std::this_thread::sleep_for(1s);
std::cout << "1st task finishedn";
});
auto fut2 = std::async(std::launch::async, [](auto fut2) {
return "2nd task finishedn";
}, std::move(fut1));
std::cout << fut2.get();
Два варианта поведения деструктора future - пример
133
auto fut1 = std::async(std::launch::async, [] {
std::this_thread::sleep_for(1s);
std::cout << "1st task finishedn";
});
auto fut2 = std::async(std::launch::async, [](auto fut2) {
return "2nd task finishedn";
}, std::move(fut1));
std::cout << fut2.get();
$ ./prog
1st task finished
2nd task finished
Два варианта поведения деструктора future - пример
134
auto fut1 = std::async(std::launch::async, [] {
std::this_thread::sleep_for(1s);
std::cout << "1st task finishedn";
}).share();
auto fut2 = std::async(std::launch::async, [](auto fut2) {
return "2nd task finishedn";
}, fut1);
std::cout << fut2.get();
$ ./prog
2nd task finished
1st task finished
Два варианта поведения деструктора future - пример
135
auto fut1 = std::async(std::launch::async, [] {
std::this_thread::sleep_for(1s);
std::cout << "1st task finishedn";
}).share();
auto fut2 = std::async(std::launch::async, [](auto fut2) {
return "2nd task finishedn";
}, fut1);
std::cout << fut2.get();
// Деструктор futs может блокироваться
std::vector<std::future<void>> futs;
// Объект может блокироваться при уничтожении
class Widget {
private:
std::shared_future<double> fut;
};
Неблокирующие
будущие результаты и
другие перспективные
примитивы
синхронизации
136
Неблокирующие будущие результаты (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"; }137
Неблокирующие будущие результаты (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();
138
Неблокирующие будущие результаты (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() для
ожидания результата.
139
Неблокирующие будущие результаты (then)
$ ./prog
begin thinking over the answer...
continue thinking over the answer...
waiting for the answer...
do some useful stuff
answer: still thinking...
number 42
do some other useful stuff
140
Неблокирующие будущие результаты (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();
});
do_some_stuff();
f.then([](auto f){
std::cout << "answer: " << f.get() << std::endl;
});
do_some_other_stuff();
141
вызывающий поток блокируется
Неблокирующие будущие результаты (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(); 142
Неблокирующие будущие результаты (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();
вызывающий поток не блокируется
143
Неблокирующие будущие результаты (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();
вызывающий поток блокируется
144
Неблокирующие будущие результаты (then)
$ g++ -Wall -pedantic -pthread -lboost_system 
-lboost_thread -std=c++14 -O2 prog.cpp -o prog
$ ./prog
waiting for the answer...
do some useful stuff
begin thinking over the answer...
continue thinking over the answer...
still thinking...
answer: number 42
do some other useful stuff
145
Неблокирующие будущие результаты (then)
Блокирующие future Неблокирующие future
f2
f3
f1
f
▪ устанавливается явный
порядок выполнения
▪ нет блокировок
▪ поток один
▪ порядок выполнения
неопределён
▪ возможны блокировки
▪ для каждой задачи
создаётся отдельный поток 146
Ожидание выполнения всех задач (when_all)
f2
f1
f3
Будущий результат f4 зависит от выполнения всех
будущих результатов f1, f2, f3 и начинает выполняться
после завершения выполнения задач, им соответствующих
(подобно барьерной синхронизации).
f4
147
Ожидание выполнения всех задач (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 1n"; }));
task_chunk.emplace_back(boost::async([]()
{ std::cout << "hello from task 2n"; }));
task_chunk.emplace_back(boost::async([]()
{ std::cout << "hello from task 3n"; }));
auto join_task = boost::when_all(task_chunk.begin(),
task_chunk.end());
do_some_stuff();
join_task.wait();
148
Ожидание выполнения всех задач (when_all)
std::vector<boost::future<int>> task_chunk;
task_chunk.emplace_back(boost::async(boost::launch::async,
[](){ std::cout << "hello from task 1n"; return 10; }));
task_chunk.emplace_back(boost::async(boost::launch::async,
[](){ std::cout << "hello from task 2n"; return 20; }));
task_chunk.emplace_back(boost::async(boost::launch::async,
[](){ std::cout << "hello from task 3n"; 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>>>
149
Ожидание выполнения всех задач (when_all)
f2
f1
f3
f4
f5
150
Ожидание выполнения всех задач (when_all)
$ g++ -Wall -pedantic -pthread -lboost_system 
-lboost_thread -std=c++14 -O2 prog.cpp -o prog
$ ./prog
hello from task 1
hello from task 3
hello from task 2
do some useful stuff
result: 42
151
Ожидание выполнения какой-либо задачи (when_any)
f2
f1
f3
Будущий результат f4 зависит от выполнения одного из
будущих результатов f1, f2, f3 и начинает выполняться
после завершения выполнения хотя бы одной задачи
(подобно синхронизации “эврика”).
f4
152
Ожидание выполнения какой-либо задачи (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;
153
join_task
имеет тип
future<
vector<
future<T>>>
Ожидание выполнения какой-либо задачи (when_any)
$ g++ -Wall -pedantic -pthread -lboost_system 
-lboost_thread -std=c++14 -O2 prog.cpp -o prog
do some useful stuff
result: 2.71828
do some useful stuff
result: 0.693147
do some useful stuff
result: 3.14159
154
Возможные варианты:
Реализация
высокоуровневых
средств синхронизации
155
Мьютексы и очереди задач
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;
}
};
Очереди задачБлокировка мьютекса
156
Мьютексы и очереди задач
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;
});
}
};
Блокировка мьютекса Очереди задач
157
Мьютексы и очереди задач
Блокировка мьютекса
▪ потоки блокируются
▪ имеется возможность
дедлока
▪ небольшая
масштабируемость
▪ порядок следования
сообщения в логе отличается
от последовательности
поступления
Очереди задач
▪ потоки не блокируются
▪ отсутствует возможность
дедлока
▪ высокая масштабируемость
▪ порядок следования
сообщения в логе совпадает
с фактическим
158
Паттерн: потокобезопасная обёртка над данными
Требования к потокобезопасным “обёрткам”:
1. Сохранение интерфейса
widget w; => w.func("hi folks!");
wrapper<widget> w; => w.func("hi folks!");
2. Универсальность. Заранее может быть неизвестны
методы, которые необходимо обернуть. Некоторые методы
сложно обернуть: конструкторы, операторы, шаблоны и т.д.
3. Поддержка транзакций
account.deposit(Sergey, 1000)
account.withdraw(Ivan, 1000);
log.print("user ", username, "data ");
log.print("time ", logmsg);
Реализация отдельных методов может не обеспечить
необходимую гранулярность.
159
Паттерн: обёртка над данными с блокировками
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;
}
}; 160
Потокобезопасная обёртка над данными с блокировками
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);
}
};
161
Потокобезопасная обёртка над данными с блокировками
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 << "donen";
162
Потокобезопасная обёртка над данными с блокировками
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 = 3
done 163
Потокобезопасная обёртка над данными с блокировками
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 << "donen";
}); 164
Потокобезопасная обёртка над данными с блокировками
i = 0
i = 2
hi from 2
hi from 0
i = 1
hi from 1
i = 3
hi from 3
i = 4
hi from 4
done
i = 0
i = 3
i = 2
hi from 2
i = 1
hi from 1
hi from 3
i = 4
hi from 4
hi from 0
done
i = 0
hi from 0
i = 4
hi from 4
i = 2
hi from 2
i = 3
hi from 3
i = 1
hi from 1
done
i = 0
hi from 0
i = 2
hi from 2
i = 3
hi from 3
i = 1
hi from 1
i = 4
hi from 4
done
165
Потокобезопасная обёртка над данными на основе очереди задач
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); }); }
}; 166
Потокобезопасная обёртка над данными на основе очереди задач
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 << "donen";
167
Потокобезопасная обёртка над данными на основе очереди задач
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
done
start 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 168
Потокобезопасная обёртка над данными на основе очереди задач
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::deferred, [&, 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 << "donen";
169
start i = 0
start i = 0 i = 1
start i = 0 i = 1 i = 2
start i = 0 i = 1 i = 2 i = 3
start i = 0 i = 1 i = 2 i = 3 i = 4
done
Потокобезопасная обёртка над данными на основе очереди задач
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 << "donen";
}); 170
Потокобезопасная обёртка над данными на основе очереди задач
i = 0
hi from 0
i = 2
hi from 2
i = 3
hi from 3
i = 4
hi from 4
i = 1
hi from 1
done
i = 0
hi from 0
i = 2
hi from 2
i = 3
hi from 3
i = 4
hi from 4
i = 1
hi from 1
done
i = 0
i = 1
hi from 1
hi from 0
i = 2
hi from 2
i = 4
hi from 4
i = 3
hi from 3
done
i = 0
hi from 0
i = 2
hi from 2
i = 1
i = 3
hi from 3
hi from 1
i = 4
hi from 4
done
171
Потокобезопасная обёртка над данными на основе очереди задач
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() позволяет вернуть значение при вызове
функции:
172
Потокобезопасная обёртка над данными на основе очереди задач
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(); }
173
Потокобезопасная обёртка над данными на основе очереди задач
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 += "donen";
std::cout << s;
return s;
});
std::cout << "return value: " << f.get() << std::endl; 174
Мьютексы и очереди задач
Блокировка мьютекса
▪ потоки блокируются
▪ имеется возможность
дедлока
▪ небольшая
масштабируемость
▪ порядок следования
сообщения в логе отличается
от последовательности
поступления
Очереди задач
▪ потоки не блокируются
▪ отсутствует возможность
дедлока
▪ высокая масштабируемость
▪ порядок следования
сообщения в логе совпадает
с фактическим
175
Потокобезопасная обёртка на основе очереди задач - применение
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; // обёртка для потокобезопасного
}; // выполнения операций с данными
176

Weitere ähnliche Inhalte

Was ist angesagt?

Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...Alexey Paznikov
 
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...Alexey Paznikov
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksMikhail Kurnosov
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castRoman Orlov
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияSergey Platonov
 
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаAlexey Paznikov
 
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Platonov Sergey
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksMikhail Kurnosov
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...Alexey Paznikov
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Alexey Paznikov
 
Максим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsМаксим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsPlatonov Sergey
 
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Platonov Sergey
 
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!Yandex
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Yauheni Akhotnikau
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерSergey Platonov
 

Was ist angesagt? (20)

Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
Кулагин И.И., Пазников А.А., Курносов М.Г. Оптимизация информационных обменов...
 
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
ПВТ - весна 2015 - Лекция 2. POSIX Threads. Основные понятия многопоточного п...
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_cast
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизация
 
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курса
 
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
 
Максим Хижинский Lock-free maps
Максим Хижинский Lock-free mapsМаксим Хижинский Lock-free maps
Максим Хижинский Lock-free maps
 
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
Игорь Кудрин, «Используем неизменяемые данные и создаем качественный код»
 
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
Алексей Куканов — Параллелизм в C++: управляйте приложением, а не потоками!
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптер
 

Ähnlich wie ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхронизация, будущие результаты

Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
 
Cpp0x Introduction
Cpp0x IntroductionCpp0x Introduction
Cpp0x IntroductionFedor Vompe
 
Rust: абстракции и безопасность, совершенно бесплатно
Rust: абстракции и безопасность, совершенно бесплатноRust: абстракции и безопасность, совершенно бесплатно
Rust: абстракции и безопасность, совершенно бесплатноOpen-IT
 
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Полезный constexpr - Антон ПолухинC++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухинcorehard_by
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonovComputer Science Club
 
Статический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутСтатический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутAndrey Karpov
 
Иван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программированиеИван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программированиеYandex
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Sergey Platonov
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Sergey Platonov
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Yandex
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклыSergey Nemchinsky
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаAndrey Karpov
 
Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Yandex
 
CPU Performance in Java.
CPU Performance in Java.CPU Performance in Java.
CPU Performance in Java.Dzmitry Hil
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кодаAndrey Karpov
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 

Ähnlich wie ПВТ - весна 2015 - Лекция 5. Многопоточное программирование в С++. Синхронизация, будущие результаты (20)

Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий Леванов
 
Cpp0x Introduction
Cpp0x IntroductionCpp0x Introduction
Cpp0x Introduction
 
Rust: абстракции и безопасность, совершенно бесплатно
Rust: абстракции и безопасность, совершенно бесплатноRust: абстракции и безопасность, совершенно бесплатно
Rust: абстракции и безопасность, совершенно бесплатно
 
Programming c++ (begin-if-else)
Programming c++ (begin-if-else)Programming c++ (begin-if-else)
Programming c++ (begin-if-else)
 
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Полезный constexpr - Антон ПолухинC++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
 
20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov20130429 dynamic c_c++_program_analysis-alexey_samsonov
20130429 dynamic c_c++_program_analysis-alexey_samsonov
 
JavaDay'14
JavaDay'14JavaDay'14
JavaDay'14
 
Статический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутСтатический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минут
 
Иван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программированиеИван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программирование
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++
 
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
Евгений Крутько — Опыт внедрения технологий параллельных вычислений для повыш...
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклы
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кода
 
Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11
 
CPU Performance in Java.
CPU Performance in Java.CPU Performance in Java.
CPU Performance in Java.
 
Algo 00
Algo 00Algo 00
Algo 00
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 

Mehr von Alexey Paznikov

Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Alexey Paznikov
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Alexey Paznikov
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIAlexey Paznikov
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Alexey Paznikov
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыAlexey Paznikov
 
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...Alexey Paznikov
 
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsAlexey Paznikov
 
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюAlexey Paznikov
 
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияAlexey Paznikov
 
ПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаAlexey Paznikov
 
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 9
 ТФРВС - весна 2014 - лекция 9 ТФРВС - весна 2014 - лекция 9
ТФРВС - весна 2014 - лекция 9Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6Alexey Paznikov
 

Mehr von Alexey Paznikov (17)

Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPI
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
 
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
 
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
 
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
 
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
 
ПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курса
 
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
 
ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11
 
ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10
 
ТФРВС - весна 2014 - лекция 9
 ТФРВС - весна 2014 - лекция 9 ТФРВС - весна 2014 - лекция 9
ТФРВС - весна 2014 - лекция 9
 
ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8
 
ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7
 
ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6
 

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

  • 1. Лекция 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring Параллельные вычислительные технологии Весна 2015 (Parallel Computing Technologies, PCT 15)
  • 3. О дивный новый [параллельный] мир! #include <iostream> #include <thread> void hello() { // функция, которая реализует поток std::cout << "hello brave new world!n"; } int main() { std::thread t(hello); // создаём поток t.join(); // дожидаемся завершения } 3
  • 4. Запуск потока 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
  • 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 worldn"; }); thr.join(); “Most vexing parse” 5
  • 6. Отсоединённый поток 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
  • 7. Ожидание завершения потока в случае исключения std::thread thr(myfunc); try { std::cout << "hello"; // ... throw "error"; } catch (...) { thr.join(); // не забыть дождаться завершения std::cout << "exception catchedn"; return 1; } thr.join(); 7
  • 8. RAII - передача ресурса есть инициализация 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); do_some_work(); } // t.join() 8
  • 9. Запуск нескольких потоков и ожидание завершения 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
  • 10. Запуск нескольких потоков и идентификаторы потоков 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
  • 11. Передача аргументов функции потока 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
  • 12. Передача аргументов функции потока 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
  • 13. Передача аргументов функции потока 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
  • 14. Передача аргументов функции потока 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
  • 15. Передача аргументов функции потока 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
  • 16. Передача аргументов функции потока по ссылке 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
  • 17. Передача управления потоком 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
  • 18. Передача аргументов 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
  • 19. Передача управления потоком 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
  • 20. Передача управления потоком std::thread foo() { std::thread thr([](){ std::cout << "threadn"; }); return thr; // перемещение } void bar(std::thread thr) { thr.join(); } int main() { std::thread thr = foo(); bar(std::move(thr)); // перемещение return 0; } 20
  • 21. RAII - передача ресурса есть инициализация 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))}; } // ~scoped_thread() 21
  • 22. RAII - передача ресурса есть инициализация try { scoped_thread t{std::thread(func((local))}; // ... if (cond) throw "error"; // ~scoped_thread() } catch (...) { std::cout << "exception catchedn"; return 1; } 22
  • 23. Параллельная версия алгоритма accumulate Поток 1 Поток 2 Поток num_threads CPU CPU CPU accumulate_block accumulate_block accumulate_block 23
  • 24. Параллельная версия алгоритма 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); } }; 24
  • 25. Параллельная версия алгоритма 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); аппаратный предел числа потоков (ядер) макс. число потоков число потоков размер блока 25
  • 26. Параллельная версия алгоритма 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); } последний блок (+остаток) 26
  • 27. Параллельная версия алгоритма 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; } 27
  • 29. Мьютексы в С++ 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(); } 29
  • 30. Мьютексы в С++ 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(); } 30
  • 31. Мьютексы в С++ 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
  • 32. Мьютексы в С++ 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 Любой код, имеющий доступ к указателю или ссылке, может делать с ним всё, что угодно, не захватывая мьютекс. Нельзя передавать указатели и ссылки на защищённые данные за пределы области видимости блокировки никаким образом. 32
  • 33. Адаптация интерфейсов к многопоточности 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
  • 34. Адаптация интерфейсов к многопоточности 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&&); }; 34
  • 35. Адаптация интерфейсов к многопоточности 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(); // ... } 35
  • 36. Адаптация интерфейсов к многопоточности std::vector<int> result; mystack.pop(result); 1. Передавать ссылку в функцию pop 2. Потребовать наличия копирующего или перемещающего конструктора, не возбуждающего исключений (доказано, что можно объединить pop и top, но это можно сделать только если конструкторы не вызывают исключений) 3. Возвращать указатель на вытолкнутый элемент 4. Одновременно 1 и один из вариантов 2 или 3 std::shared_ptr<T> pop() 36
  • 37. Потокобезопасный стек 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 отсутствует // -- интерфейс предельно упрощён -- }; 37
  • 38. Потокобезопасный стек 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(); } 38
  • 39. Дедлоки: захват нескольких мьютексов 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 - “всё или ничего” 39
  • 40. Дедлоки: иерархические мьютексы hierarchical_mutex(100) hierarchical_mutex(50) hierarchical_mutex(30) 1 -> 2 -> 3 1 2 3 2 -> 3 3 -> 1 3 -> 2 порядок запирания 40
  • 41. Дедлоки: иерархические мьютексы hierarchical_mutex(100) hierarchical_mutex(50) hierarchical_mutex(30) 1 2 3 порядок запирания 1 -> 2 -> 3 2 -> 3 3 -> 1 3 -> 2 41
  • 42. Дедлоки: иерархические мьютексы - пример 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(); // вызов недопустим } 42
  • 43. Иерархические мьютексы - возможная реализация class hier_mutex { // hierarchical mutex private: 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; } 43
  • 44. Иерархические мьютексы - возможная реализация 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); 44
  • 45. Блокировка с помощью 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; } 45
  • 46. Блокировка с помощью 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; } Минимизация гранулярности блокировки! 46
  • 47. Блокировка с помощью 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); // вывести результат } Минимизация блокировок! ▪ блокировать данные, а не операции ▪ удерживать мьютекс столько, сколько необходимо ▫ тяжёлые операции (захват другого мьютекса, ввод/вывод и т.д.) - вне текущей критической секции 47
  • 48. Однократный вызов и отложенная инициализация 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() { /* ... */ } } А если несколько потоков? 48
  • 49. Однократный вызов и отложенная инициализация 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() { /* ... */ } }; Защищать только инициализацию 49
  • 50. Однократный вызов и отложенная инициализация 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); } двойная проверка 50
  • 51. Однократный вызов и отложенная инициализация 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() { /* ... */ } } 51
  • 52. 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; } }; 52
  • 53. 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; } 53
  • 54. Рекурсивные мьютексы ▪ std::recursive_mutex ▪ мьютекс можно запирать несколько раз в одном потоке ▪ освобождать мьютекс требуется столько раз, сколько он был захвачен ▪ использование - аналогично std::mutex (std::lock_guard, std::unique_lock, …) 54
  • 55. Условные переменные ▪ std::condition_variable, std:: condition_variable_any условная переменная, необходимо взаимодействие с мьютексом (condition_variable) или с любым классом (condition_variable_any), подобным мьютексу ▪ wait - ожидание условия ▪ wait_for, wait_until - ожидание условия заданное время или до заданного момента ▪ notify_one - сообщить одному потоку ▪ notify_all - сообщить всем потокам 55
  • 56. Условные переменные - производитель-потребитель 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); } } 56
  • 59. Будущие результаты (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 59
  • 60. Будущие результаты (future) struct Widget { void foo(std::string const&, int); int bar(std::string const&); int operator()(int); }; Widget w; // Вызывается foo("carpe dieum", 2014) для объекта w auto f1 = std::async(&Widget::foo, &w, "carpe diem", 2014); // Вызывается bar("carpe dieum", 2014) для объекта tmp = w auto f2 = std::async(&Widget::bar, w, "carpe diem"); // Вызывается tmp.operator(2014), где tmp = w auto f3 = std::async(Widget(), 2014); // Вызвается w(1234) auto f4 = std::async(std::ref(w), 2014); 60
  • 61. Будущие результаты (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); 61
  • 62. Будущие результаты (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; // только ожидание // результата 62
  • 63. Упакованные задачи task 1 task 2 task 3 package 1 package 2 package 3 63
  • 64. Упакованные задачи ▪ Шаблон 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) 64
  • 65. Упакованные задачи - пример (пул задач) task package task package tasks.push_back( std::move(task)); std::packaged_task<void()> task = std::move(tasks.front()); batch_systemadd_task 65 task()
  • 66. Упакованные задачи - пример 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(); // запуск задачи } } 66
  • 67. Упакованные задачи - пример 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 << "voxn"; } void say_populi() { std::cout << "populin"; } void say_dei() { std::cout << "dein"; } void write_word() { std::string s; std::cin >> s; } 67
  • 68. Упакованные задачи - пример 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; } 68
  • 69. Упакованные задачи - пример, возможные варианты $ ./prog vox populi , $ ./prog vox populi , vox dei $ ./prog vox populi , vox 69
  • 71. “Обещанные” результаты (std::promise) std::futurestd::promise<...> p Msg received!p.get_future().wait() ok, let’s move! p.set_value(msg) 71
  • 72. “Обещанные” результаты (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(); } 72
  • 73. “Обещанные” результаты (std::promise) - пример 1 mainmain thread th1print_value prom.set_value() print_value th1(print_value, std::ref(fut)) fut.get() compute_value работа ожидание создание/завершение потоков синхронизация 73
  • 74. “Обещанные” результаты (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(); }); 74
  • 75. “Обещанные” результаты (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(); } 75
  • 76. “Обещанные” результаты (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() создание/завершение потоков синхронизация 76
  • 77. “Обещанные” результаты (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 77
  • 80. Проблемы с параллелизмом на основе потоков int doWork(); std::thread t(doWork); // 1 // или auto fut = std::async(doWork); // 2 80 ▪ Вариант, основанный на задаче (2), предпочтительней, т.к. предполагает возвращаемое значение, которое можно получить fut.get(). ▪ Если doWork выбрасывает исключение, то get() позволяет обработать исключения, в то время как в первом случае выброс исключения приведёт к завершению программы.
  • 81. Проблемы с параллелизмом на основе потоков 81 Параллелизм задач находится на более высоком уровне абстракции по сравнению с параллелизмом потоков, освобождает программиста от деталей реализации: ▪ Аппаратные потоки (software threads) - те, которые действительно выполняют вычисления (по числу ядер). ▪ Программные потоки (hardware threads) - потоки, которые планируются ОС и выполняются на аппаратных потоках. ▫ Легковесные потоки (lightweight threads) - потоки, которые выполняются целиком в пространстве пользователя. ▪ std::thread - объекты С++, которые соответствуют определённым программным потокам, с которыми можно выполнять операции join и detach
  • 82. Ограниченность количества программных потоков 82 Программные потоки - ограниченный ресурс. Попытка создать больше заданного числа потоков вызовет исключение, даже если int doWork() noexcept; std::thread t(doWork); // может быть исключение! ▪ Запустить doWork в текущем потоке? ▪ Или подождать, пока освободится программный поток? :(
  • 83. Перегруженность аппаратных потоков (oversubscription) 83 Состояние перегруженности аппаратных потоков oversubscription возникает, когда в системе большое количество runnable-потоков. Планировщик ОС выделяет программным потокам порции (time-slice) процессорного времени. После окончания порции происходит переключение контекста (context switch), особенно в случае, когда поток назначается на разные ядра: ▪ Кэш-память не загружена, большое количество промахов по кэшу. ▪ Запуск нового потока на ядре перезаписывает записи для старого потока, который, вероятно, будет опять назначен на это ядро. Это опять приводит к промахам по кэшу.
  • 84. Перегруженность аппаратных потоков (oversubscription) 84 Выбор оптимального количества потоков для избежания перегруженности зависит от: ▪ Момента, когда программа переходит из региона с вводом-выводом к области с вычислениями. ▪ Стоимости переключения контекста ▪ Того, насколько эффективно потоки используют кэш ▪ Аппаратной архитектуры
  • 85. Перегруженность аппаратных потоков (oversubscription) 85 Выбор оптимального количества потоков для избежания перегруженности зависит от: ▪ Момента, когда программа переходит из региона с вводом-выводом к области с вычислениями. ▪ Стоимости переключения контекста ▪ Того, насколько эффективно потоки используют кэш ▪ Аппаратной архитектуры Сделай жизнь легче, используй std::async! Ответственность за управление потоками лежит на плечах разработчика стандартной библиотеки!
  • 86. Long live std::async! 86 std::async позволяет создать неограниченное количество асинхронных функций ▪ Вызов std::async не гарантирует создание нового программного потока (политики async и deferred). ▪ Асинхронная функция может быть запущена, например, в том же потоке, где и вызывается get, wait,позволяя избежать перегруженности (oversubscription). ▪ Возможность work-stealing (легковесных потоков)
  • 87. Long live std::async! But... 87 std::async позволяет создать неограниченное количество асинхронных функций ▪ Вызов std::async не гарантирует создание нового программного потока (политики async и deferred). ▪ Асинхронная функция может быть запущена, например, в том же потоке, где и вызывается get, wait,позволяя избежать перегруженности (oversubscription). ▪ Возможность work-stealing (легковесных потоков) Но std::async не универсальное средство. Недостатки: ▪ Приводит к возможному дисбалансу загрузки. Планирование происходит на двух уровнях: ОС и программы. ▪ Не подходит для некоторых целей (напр., GUI)
  • 88. Когда таки нужно использовать потоки 88 ▪ Нужно воспользоваться функционалом низкоуровневой реализации потоков (например, std:: thread::native_handle) ▪ Нужно оптимизировать использвоание потоков в программе. Допустим, если вы разрабатываете сервер, который будет запускаться на заданной архитектуре. ▪ Вы хотите реализовать потоки на архитектуре, где пока нет реализации C++-concurrency. Но это редко. В большинстве случаев смело разрабатывайте программы на основе задач, а не на основе потоков.
  • 89. Быстрая сортировка в духе функционального прог-я 89
  • 90. Быстрая сортировка в духе функционального прог-я template<typename T> std::list<T> sequential_quick_sort(std::list<T> 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; } 90
  • 91. Быстрая сортировка в духе функционального прог-я lower part input input pivot splice divide_point new_lower new_higher 91
  • 92. Параллельный алгоритм быстрой сортировки template<typename T> std::list<T> parallel_quick_sort(std::list<T> 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; } 92
  • 94. Помните про политику запуска задачи 94 ▪ std::launch::async - функция будет запущена асинхронного, т.е. в отдельном потоке T1main thread T2func fut.get() ▪ std::launch::deferred - функция может быть запущена только, когда вызан методв get или wait для объекта future в потоке, вызывающем get (wait). T1main thread T2func fut.get() async async
  • 95. Помните про политику запуска задачи 95 ▪ std::launch::async - функция будет запущена асинхронного, т.е. в отдельном потоке ▪ std::launch::deferred - функция может быть запущена только, когда вызан методв get или wait для объекта future в потоке, вызывающем get (wait). auto fut = std::async(func); // использовать политику // запуска по умолчанию ▪ Нельзя предугадать, будет ли func выполняться асинхронно ▪ Нельзя предугадать, будет ли func запущена на потоке, отличном, от потока, вызывающего get (wait) ▪ Нельзя предугадать, что func будет выполнена.
  • 96. Проблема: std::async и Thread Local Storage (TLS) 96 auto fut = std::async([](){ // Может использоваться thread_local local_var; // TLS для независимого ... // потока }); ... fut.get(); // а может и для этого! Политика запуска по умолчанию конфликтует с использованием переменных thread_local:
  • 97. Проблема: std::async и цикла на основе wait_for 97 using namespace std::literals; // C++14 суффиксы auto fut = std::async([]() { std::this_thread::sleep_for(1s); }); // Цикл, ожидающий выполнения std::async, // может не завершиться while (fut.wait_for(100ms) != std::future_status::ready) { /* делать что-то асинхронного */} Использование циклов на основе вызова wait_for или wait_until может привести к вечному ожиданию, если задача будет запущена как отложенная (std::launch:: deferred):
  • 98. Проблема: std::async и цикла на основе wait_for - решение 98 auto fut = std::async([]() { std::this_thread::sleep_for(1s); }); if (fut.wait_for(0s) == std::future_satus::deferred) { fut.get(); // ожидаем результата ... } else { while (fut.wait_for(100ms) != std::future_status::ready) { // делать какую-то работу асинхронного, // пока ждём завершения выполнения задачи } // здесь fut готово }
  • 99. Когда использовать политику запуска по умолчанию 99 ▪ Задача не требует асинхронного запуска в отдельном потоке, отличном от вызывающего get (wait). ▪ Не важно, thread_local-переменные какого потока будут использоваться. ▪ Или есть гарантия, что get (wait) будут вызваны для объекта future, возвращённого std::async, или задача может быть вовсе на запущена. ▪ При использовании wait_for или wait_until допускается возможность отложенного запуска задачи. Если какие-либо пункты не выполняются, лучше гарантировать асинхронный запуск задачи через передачу std::launch::async
  • 101. Joinable и unjoinable 101 Объект std::thread может пребывать в двух состояниях: ▪ joinable: объект соответствует потоку, который выполняется или может быть запущен. ▪ unjoinable: объект, с которым нельзя выполнить операцию join: ▫ выполнен конструктур по умолчанию для std:: thread, т.е. std::thread может не имеет функции для выполнения и поэтому не соответствует реальному потоку. ▫ std::thread, который был перемещён (moved) ▫ std::thread, который был присоединён (joined) ▫ std::thread, который был отсоденинён (detached)
  • 102. Проблема с joinable-потоками 102 constexpr auto n = 10'000'000; // C++14-style bool doWork(std::function<bool(int)> pred, // условие int maxVal = n) { std::vector<int> goodVals; // значения, удовл. условию std::thread t([&pred, maxVals, &goodVals]{ for (auto i = 0; i <= maxVals; i++) { if (pred(i)) goodVals.push_back(i); } }); auto nh = t.native_handle(); ... // низкоуровневые манипуляции с потоком if (conditionAreSatisfied()) { t.join(); performComputation(goodVals); return true; } return false; }
  • 103. Проблема с joinable-потоками 103 constexpr auto n = 10'000'000; // C++14-style bool doWork(std::function<bool(int)> pred, // условие int maxVal = n) { std::vector<int> goodVals; // значения, удовл. условию std::thread t([&pred, maxVals, &goodVals]{ for (auto i = 0; i <= maxVals; i++) { if (pred(i)) goodVals.push_back(i); } }); auto nh = t.native_handle(); ... if (conditionAreSatisfied()) { t.join(); performComputation(goodVals); return true; } return false; } sched_param sch; int policy; pthread_getschedparam(nh, &policy, &sch); sch.sched_priority = 20; if (pthread_setschedparam(nh, SCHED_FIFO, &sch)) { std::cout << "Failed to setschedparam: " << std::strerror(errno) << 'n'; }
  • 104. Проблема с joinable-потоками constexpr auto n = 10'000'000; // C++14-style bool doWork(std::function<bool(int)> pred, // условие int maxVal = n) { std::vector<int> goodVals; // значения, удовл. условию std::thread t([&pred, maxVals, &goodVals]{ for (auto i = 0; i <= maxVals; i++) { if (pred(i)) goodVals.push_back(i); } }); auto nh = t.native_handle(); ... if (conditionAreSatisfied()) { t.join(); performComputation(goodVals); return true; // ok, т.к. был t.join() } return false; // не было t.join()! выброс исключения } // и аварийное завершение программы 104
  • 105. Проблема с joinable-потоками Как можно решить проблему (наивно): ▪ Неявный join. Деструктор std::thread будет ожидать завершения потока. Но это приведёт к неочевидному коду, например, когда поток ждёт завершения doWork, уже зная, что условие не выполнено. ▪ Неявный detach. Деструктор разрывает связь между std::thread и потоком выполнения. Поток продолжает работать. В этом случае, например, при завершении функции doWork, поток продолжает работать. Этот поток может использовать автоматические переменные из стека doWork. Поэтому стандарт запретил уничтожение потока в сотоянии joinable: деструктур такого объекта вызывает завершение программы. 105
  • 106. Проблема с joinable-потоками 106 ▪ Программист должен следить за тем, что объект должен находиться в состоянии unjoinable вне его области видимости. ▪ Обеспечение этого требования - задача непростая, поскольку требует отслеживания всех выходов из функции через return, continue, break, goto, exception. ▪ Необходимо обеспечить выполнение определённого действия каждый раз при выходе из блока.
  • 107. Make it RAII! ▪ Программист должен следить за тем, что объект должен находиться в состоянии unjoinable вне его области видимости. ▪ Обеспечение этого требования - задача непростая, поскольку требует отслеживания всех выходов из функции через return, continue, break, goto, exception. ▪ Необходимо обеспечить выполнение определённого действия каждый раз при выходе из блока. Решение: RAII-объекты (Resouce Acquisition Is Initialization), (к которым относятся std::unique_ptr, std::shared_ptr, std::lock_guard, std::fstream и др.), деструктор которых содержит необходимое действие. 107
  • 108. RAII 108 class ThreadRAII { public: enum class DestrAction { join, detach }; ThreadRAII(std::thread&& t, DestrAction a): action{a}, t{std::move(t)} { } ~ThreadRAII() { // действие выполняется в деструкторе if (t.joinable()) { if (action == DestrAction::join) { t.join(); } else { t.detach(); } } } std::thread& get() { return t; } private: DestrAction action; // action in destuctor std::thread t; };
  • 109. RAII 109 bool doWork(std::function<bool(int)> pred, int maxVal = n) { std::vector<int> goodVals; // значения, удовл. условию ThreadRAII t{ // использовать RAII-объект std::thread([&stencil, maxVals, &goodVals]{ for (auto i = 0; i <= maxVals; i++) { if (pred(i)) goodVals.push_back(i); } }), ThreadRAII::DestrAction::join // действие }; // в деструкторе auto nh = t.get().native_handle(); ... if (conditionAreSatisfied()) { t.get().join(); performComputation(goodVals); return true; } return false; }
  • 111. “Обещанные” результаты (std::promise) Msg received! ok, let’s move! cv.notify_one() 111 Условные переменные? std::unique_lock<std::mutex> lk(m); cv.wait(lk) cv
  • 112. Недостатки синхр-ции на основе условных переменных 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); } } 112
  • 113. Недостатки синхр-ции на основе условных переменных ▪ Необходимость использования мьютекса std::unique_lock<std::mutex> lock(mut); cond.wait(lock, ...); А что, если потоки выполняют код, который не нуждается в блокировке мьютекса? Например, один поток инициализирует структуру, после чего сообщает другому, что структура готова. ▪ Пропущенный сигнал Поток может отправить сигнал (notify_one/all) тогда, когда другой поток ещё не начал его ожидать. ▪ Ложное пробуждение (spurious wakeup) Поток может проснуться тогда, когда сигнал не был отправлен Или когда он был отправлен потока, а затем условие перестало выполняться. Поэтому нужна дополнительная проверка: cond.wait(lock, []{return !widget_queue.empty();})); А что, если поток не может проверить условие?! 113
  • 114. Недостатки синхр-ции на основе условных переменных std::atomic<bool> flag(false); ... flag = true; ... while (!flag); // активное ожидание! :( ... 114 Для решения проблемы ложного пробуждения можно использовать атомарный флаг: Или так: { flag = true; cv.notify_one(); } { cv.wait(lk, [] { return flag; }); // код “с запашком” :( }
  • 115. “Обещанные” результаты (std::promise) std::futurestd::promise<...> p p.get_future().wait() p.set_value() 115 ok, let’s move!
  • 116. “Обещанные” результаты (std::promise) std::futurestd::promise<...> p p.get_future().wait() p.set_value() 116 ok, let’s move! ▪ Не требует мьютексов ▪ Не нуждается в атомарных флагах ▪ Не использует активного ожидания ▪ Не зависит от порядка выполнения wait, set_value
  • 117. “Обещанные” результаты (std::promise) std::futurestd::promise<...> p p.get_future().wait() p.set_value() 117 ok, let’s move! ▪ Не требует мьютексов ▪ Не нуждается в атомарных флагах ▪ Не использует активного ожидания ▪ Не зависит от порядка выполнения wait, set_value ▪ Надо заботиться о поведении деструктора future ▪ Можно отправить сигнал только один раз
  • 118. “Обещанные” результаты (std::promise) - пример std::promise<void> p; void react(); // реакция на условие void detect() { // обнаружение условия std::thread t([] { p.get_future().wait(); react(); }); // делаем что-то // в это время t спит p.set_value(); // разбудить t // делаем ещё что-то t.join(); }; 118
  • 119. “Обещанные” результаты (std::promise) - пример std::promise<void> p; void react(); // реакция на условие void detect() { // обнаружение условия std::thread t([] { p.get_future().wait(); react(); }); // а что, если здесь возникнет исключение?? p.set_value(); // разбудить t // делаем ещё что-то t.join(); }; 119
  • 120. “Обещанные” результаты (std::promise) - пример std::promise<void> p; void react(); // реакция на условие void detect() { // обнаружение условия ThreadRAII t(std::thread([] { p.get_future().wait(); react(); )}); ... p.set_value(); // разбудить t // делаем ещё что-то t.join(); }; 120
  • 121. Множественная отправка сигналов std::promise<void> p; void detect() { auto sf = p.get_future().share(); std::vector<std::thread> vt; for (auto i = 0; i < threadsToRun; i++) { vt.emplace_back([sf]{ sf.wait(); react(); }); } // ... p.set_value(); // ... for (auto &t: vt) t.join(); }; 121
  • 122. Множественная отправка сигналов std::promise<void> p; void detect() { auto sf = p.get_future().share(); std::vector<std::thread> vt; for (auto i = 0; i < threadsToRun; i++) { vt.emplace_back([sf]{ sf.wait(); react(); }); } // ... p.set_value(); // ... for (auto &t: vt) t.join(); }; // RAII... 122
  • 123. Множественная отправка сигналов std::promise<void> p; void detect() { auto sf = p.get_future().share(); std::vector<ThreadRAII> vt; for (auto i = 0; i < nthreads; i++) { vt.emplace_back(std::move(ThreadRAII{ std::thread([sf]{ sf.wait(); react(); } ), ThreadRAII::DestrAction::join })); } // ... p.set_value(); }; 123
  • 124. Разделяемые будущие результаты 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; }; 124
  • 125. Разделяемые будущие результаты 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 startn" << "Thread 2 received the signal " << result2.get().count() << " ms after startn"; } 125
  • 126. Разделяемые будущие результаты shared_future main работа ожидание создание/завершение потоков синхронизация T1 T2 t2_ready. set_value start return return output ready. set_value t1_ready. set_value 126
  • 128. Хранение результата для future Деструктор объекта future ведёт себя иногда так, как будто он выполняет неявный join, а в некоторых случае - как будто выполняет неявный detach. 128 Вызываемый поток Вызывающий поток future std::promise Где хранится результат вызывающего потока? Вызывающий поток может завершиться до того, как вызываемый выполнит fut.get(), и результат не может храниться в объекте std::promise вызываемого потока. Объект future не может быть хранилищем для результата, т.к. он может быть скопирован в объекты shared_future, после чего возникает вопрос, какая из копий соответствует результату?
  • 129. Два варианта поведения деструктора future 129 Вызываемый поток Вызывающий поток future std::promise Результат вызывающего Поведение деструктора future зависит от разделяемого состояния (shared state): ▪ Деструктор последнего объекта future, указывающего на разделяемое состояние (shared state) для какой-то асинхронной задачи, блокируется до завершения выполнения этой задачи, т.е. выполняет “join”. ▪ Деструкторы всех других объектов future просто уничтожают объект future. Это аналогично вызову detach для потока. shared state
  • 130. Два варианта поведения деструктора future 130 ▪ Деструктор последнего объекта future, указывающего на разделяемое состояние, выполняет “join”, если: ▫ объект указывает на разделяемое состояние, созданное std::async ▫ задача, породившая future, была запущена асинхронно ▫ future - это последний объект future, указывающий на разделяемое состояние Зачем это нужно? ▪ Чтобы избежать неявного вызова detach для потока, в котором выполняется задача. ▪ Срабатывание деструктора не должно приводить к завершению программы (попытка компромиса)
  • 131. Два варианта поведения деструктора future 131 // Деструктор futs может блокироваться std::vector<std::future<void>> futs; // Объект может блокироваться при уничтожении class Widget { private: std::shared_future<double> fut; };
  • 132. Два варианта поведения деструктора future - пример 132 auto fut1 = std::async(std::launch::async, [] { std::this_thread::sleep_for(1s); std::cout << "1st task finishedn"; }); auto fut2 = std::async(std::launch::async, [](auto fut2) { return "2nd task finishedn"; }, std::move(fut1)); std::cout << fut2.get();
  • 133. Два варианта поведения деструктора future - пример 133 auto fut1 = std::async(std::launch::async, [] { std::this_thread::sleep_for(1s); std::cout << "1st task finishedn"; }); auto fut2 = std::async(std::launch::async, [](auto fut2) { return "2nd task finishedn"; }, std::move(fut1)); std::cout << fut2.get(); $ ./prog 1st task finished 2nd task finished
  • 134. Два варианта поведения деструктора future - пример 134 auto fut1 = std::async(std::launch::async, [] { std::this_thread::sleep_for(1s); std::cout << "1st task finishedn"; }).share(); auto fut2 = std::async(std::launch::async, [](auto fut2) { return "2nd task finishedn"; }, fut1); std::cout << fut2.get(); $ ./prog 2nd task finished 1st task finished
  • 135. Два варианта поведения деструктора future - пример 135 auto fut1 = std::async(std::launch::async, [] { std::this_thread::sleep_for(1s); std::cout << "1st task finishedn"; }).share(); auto fut2 = std::async(std::launch::async, [](auto fut2) { return "2nd task finishedn"; }, fut1); std::cout << fut2.get(); // Деструктор futs может блокироваться std::vector<std::future<void>> futs; // Объект может блокироваться при уничтожении class Widget { private: std::shared_future<double> fut; };
  • 136. Неблокирующие будущие результаты и другие перспективные примитивы синхронизации 136
  • 137. Неблокирующие будущие результаты (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"; }137
  • 138. Неблокирующие будущие результаты (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(); 138
  • 139. Неблокирующие будущие результаты (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() для ожидания результата. 139
  • 140. Неблокирующие будущие результаты (then) $ ./prog begin thinking over the answer... continue thinking over the answer... waiting for the answer... do some useful stuff answer: still thinking... number 42 do some other useful stuff 140
  • 141. Неблокирующие будущие результаты (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(); }); do_some_stuff(); f.then([](auto f){ std::cout << "answer: " << f.get() << std::endl; }); do_some_other_stuff(); 141 вызывающий поток блокируется
  • 142. Неблокирующие будущие результаты (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(); 142
  • 143. Неблокирующие будущие результаты (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(); вызывающий поток не блокируется 143
  • 144. Неблокирующие будущие результаты (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(); вызывающий поток блокируется 144
  • 145. Неблокирующие будущие результаты (then) $ g++ -Wall -pedantic -pthread -lboost_system -lboost_thread -std=c++14 -O2 prog.cpp -o prog $ ./prog waiting for the answer... do some useful stuff begin thinking over the answer... continue thinking over the answer... still thinking... answer: number 42 do some other useful stuff 145
  • 146. Неблокирующие будущие результаты (then) Блокирующие future Неблокирующие future f2 f3 f1 f ▪ устанавливается явный порядок выполнения ▪ нет блокировок ▪ поток один ▪ порядок выполнения неопределён ▪ возможны блокировки ▪ для каждой задачи создаётся отдельный поток 146
  • 147. Ожидание выполнения всех задач (when_all) f2 f1 f3 Будущий результат f4 зависит от выполнения всех будущих результатов f1, f2, f3 и начинает выполняться после завершения выполнения задач, им соответствующих (подобно барьерной синхронизации). f4 147
  • 148. Ожидание выполнения всех задач (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 1n"; })); task_chunk.emplace_back(boost::async([]() { std::cout << "hello from task 2n"; })); task_chunk.emplace_back(boost::async([]() { std::cout << "hello from task 3n"; })); auto join_task = boost::when_all(task_chunk.begin(), task_chunk.end()); do_some_stuff(); join_task.wait(); 148
  • 149. Ожидание выполнения всех задач (when_all) std::vector<boost::future<int>> task_chunk; task_chunk.emplace_back(boost::async(boost::launch::async, [](){ std::cout << "hello from task 1n"; return 10; })); task_chunk.emplace_back(boost::async(boost::launch::async, [](){ std::cout << "hello from task 2n"; return 20; })); task_chunk.emplace_back(boost::async(boost::launch::async, [](){ std::cout << "hello from task 3n"; 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>>> 149
  • 150. Ожидание выполнения всех задач (when_all) f2 f1 f3 f4 f5 150
  • 151. Ожидание выполнения всех задач (when_all) $ g++ -Wall -pedantic -pthread -lboost_system -lboost_thread -std=c++14 -O2 prog.cpp -o prog $ ./prog hello from task 1 hello from task 3 hello from task 2 do some useful stuff result: 42 151
  • 152. Ожидание выполнения какой-либо задачи (when_any) f2 f1 f3 Будущий результат f4 зависит от выполнения одного из будущих результатов f1, f2, f3 и начинает выполняться после завершения выполнения хотя бы одной задачи (подобно синхронизации “эврика”). f4 152
  • 153. Ожидание выполнения какой-либо задачи (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; 153 join_task имеет тип future< vector< future<T>>>
  • 154. Ожидание выполнения какой-либо задачи (when_any) $ g++ -Wall -pedantic -pthread -lboost_system -lboost_thread -std=c++14 -O2 prog.cpp -o prog do some useful stuff result: 2.71828 do some useful stuff result: 0.693147 do some useful stuff result: 3.14159 154 Возможные варианты:
  • 156. Мьютексы и очереди задач 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; } }; Очереди задачБлокировка мьютекса 156
  • 157. Мьютексы и очереди задач 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; }); } }; Блокировка мьютекса Очереди задач 157
  • 158. Мьютексы и очереди задач Блокировка мьютекса ▪ потоки блокируются ▪ имеется возможность дедлока ▪ небольшая масштабируемость ▪ порядок следования сообщения в логе отличается от последовательности поступления Очереди задач ▪ потоки не блокируются ▪ отсутствует возможность дедлока ▪ высокая масштабируемость ▪ порядок следования сообщения в логе совпадает с фактическим 158
  • 159. Паттерн: потокобезопасная обёртка над данными Требования к потокобезопасным “обёрткам”: 1. Сохранение интерфейса widget w; => w.func("hi folks!"); wrapper<widget> w; => w.func("hi folks!"); 2. Универсальность. Заранее может быть неизвестны методы, которые необходимо обернуть. Некоторые методы сложно обернуть: конструкторы, операторы, шаблоны и т.д. 3. Поддержка транзакций account.deposit(Sergey, 1000) account.withdraw(Ivan, 1000); log.print("user ", username, "data "); log.print("time ", logmsg); Реализация отдельных методов может не обеспечить необходимую гранулярность. 159
  • 160. Паттерн: обёртка над данными с блокировками 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; } }; 160
  • 161. Потокобезопасная обёртка над данными с блокировками 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); } }; 161
  • 162. Потокобезопасная обёртка над данными с блокировками 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 << "donen"; 162
  • 163. Потокобезопасная обёртка над данными с блокировками 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 = 3 done 163
  • 164. Потокобезопасная обёртка над данными с блокировками 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 << "donen"; }); 164
  • 165. Потокобезопасная обёртка над данными с блокировками i = 0 i = 2 hi from 2 hi from 0 i = 1 hi from 1 i = 3 hi from 3 i = 4 hi from 4 done i = 0 i = 3 i = 2 hi from 2 i = 1 hi from 1 hi from 3 i = 4 hi from 4 hi from 0 done i = 0 hi from 0 i = 4 hi from 4 i = 2 hi from 2 i = 3 hi from 3 i = 1 hi from 1 done i = 0 hi from 0 i = 2 hi from 2 i = 3 hi from 3 i = 1 hi from 1 i = 4 hi from 4 done 165
  • 166. Потокобезопасная обёртка над данными на основе очереди задач 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); }); } }; 166
  • 167. Потокобезопасная обёртка над данными на основе очереди задач 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 << "donen"; 167
  • 168. Потокобезопасная обёртка над данными на основе очереди задач 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 done start 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 168
  • 169. Потокобезопасная обёртка над данными на основе очереди задач 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::deferred, [&, 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 << "donen"; 169 start i = 0 start i = 0 i = 1 start i = 0 i = 1 i = 2 start i = 0 i = 1 i = 2 i = 3 start i = 0 i = 1 i = 2 i = 3 i = 4 done
  • 170. Потокобезопасная обёртка над данными на основе очереди задач 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 << "donen"; }); 170
  • 171. Потокобезопасная обёртка над данными на основе очереди задач i = 0 hi from 0 i = 2 hi from 2 i = 3 hi from 3 i = 4 hi from 4 i = 1 hi from 1 done i = 0 hi from 0 i = 2 hi from 2 i = 3 hi from 3 i = 4 hi from 4 i = 1 hi from 1 done i = 0 i = 1 hi from 1 hi from 0 i = 2 hi from 2 i = 4 hi from 4 i = 3 hi from 3 done i = 0 hi from 0 i = 2 hi from 2 i = 1 i = 3 hi from 3 hi from 1 i = 4 hi from 4 done 171
  • 172. Потокобезопасная обёртка над данными на основе очереди задач 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() позволяет вернуть значение при вызове функции: 172
  • 173. Потокобезопасная обёртка над данными на основе очереди задач 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(); } 173
  • 174. Потокобезопасная обёртка над данными на основе очереди задач 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 += "donen"; std::cout << s; return s; }); std::cout << "return value: " << f.get() << std::endl; 174
  • 175. Мьютексы и очереди задач Блокировка мьютекса ▪ потоки блокируются ▪ имеется возможность дедлока ▪ небольшая масштабируемость ▪ порядок следования сообщения в логе отличается от последовательности поступления Очереди задач ▪ потоки не блокируются ▪ отсутствует возможность дедлока ▪ высокая масштабируемость ▪ порядок следования сообщения в логе совпадает с фактическим 175
  • 176. Потокобезопасная обёртка на основе очереди задач - применение 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; // обёртка для потокобезопасного }; // выполнения операций с данными 176