ПВТ - весна 2015 - Лекция 6. Разработка параллельных...

51
Лекция 6. Разработка параллельных структур данных на основе блокировок Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring Параллельные вычислительные технологии Весна 2015 (Parallel Computing Technologies, PCT 15)

Upload: alexey-paznikov

Post on 23-Jul-2015

178 views

Category:

Education


2 download

TRANSCRIPT

Page 1: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Лекция 6. Разработка параллельных структур данных на основе блокировок

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

Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring

Параллельные вычислительные технологииВесна 2015 (Parallel Computing Technologies, PCT 15)

Page 2: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

2

Page 3: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

Page 4: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

▪ Инвариант - это состояние структуры, которое должно быть неизменно при любом обращении к структуре (перед любой операцией и после каждой операции)

Page 5: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

4. Сериализация

5. Голодание

6. Инверсия приоритетов

7. ...5

Page 6: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

struct empty_stack: std::exception { };

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

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

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

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

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

6

Page 7: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

7

Page 8: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

threadsafe_stack<int> stack;

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

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

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

Page 9: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

data.pop(); return value; }

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

2

1

3

4

9

Page 10: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

data.pop(); return res; }

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

data.pop(); }

1

2

3

4

5

6

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

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

10

Page 11: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

struct empty_stack: std::exception { };

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

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

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

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

DEADLOCK ?11

Page 12: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

DEADLOCK ?

DEADLOCK ?

12

Page 13: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

threadsafe_stack<int> stack;

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

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

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

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

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

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

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

13

Page 14: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

public: threadsafe_queue() {}

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

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

14

Page 15: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

15

Page 16: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

threadsafe_queue<int> queue;

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

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

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

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

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

16

Page 17: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

17

Page 18: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

18

Page 19: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

shared_ptr

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

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

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

Page 20: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

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

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

20

Page 21: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

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

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

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

21

Page 22: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

Head Tail

22

push() pop()

Page 23: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

23

Page 24: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

Page 25: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

head

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

Page 26: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

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

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

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

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

Page 27: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

Head Tail

next next

27

▪ При пустой очереди head->next и tail->next – есть один и тот же узел.

▪ В pop и push придётся тогда запирать оба мьютекса. :(

Page 28: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Модифицированная версия

Head Tail

Фиктивный узел

▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail.

▪ При очереди с одним элементом head->next и tail->next указывают на разные узлы (причём head->next == tail), в результате чего гонки не возникает. 28

Page 29: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Пустая очередь

Head Tail

Фиктивный узел

▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail.

29

Page 30: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Очередь с одним элементом

Head Tail

Фиктивный узел

▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail.

▪ При очереди с одним элементом head->next и tail->next указывают на разные узлы (причём head->next == tail), в результате чего гонки не возникает. 30

Page 31: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

public: queue(): head(new node), tail(head.get()) {} queue(const queue &other) = delete; queue &operator=(const queue &other) = delete;

node хранит указатель на данные

▪ Вводится фиктивный узел▪ При пустой очереди head и tail теперь

указывают на фиктивный узел, а не на NULL

указатель на данные вместо данных

создание первого фиктивного узла в конструкторе

31

Page 32: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

void push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value))); std::unique_ptr<node> p(new node); tail->data = new_data; node *const new_tail = p.get(); tail->next = std::move(p); tail = new_tail; }

head сравнивается с tail, а не с NULL

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

создание нового экземпляра T

создание нового фиктивного узла

записываем в старый фиктивный узел новое

значение 32

Page 33: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Добавление нового элемента в очередь (push)

tail next

data

33

Page 34: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Добавление нового элемента в очередь (push)

tail next

data

p(new node)

34

Page 35: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Добавление нового элемента в очередь (push)

tail next

data

tail->data = new_data

p(new node)

35

Page 36: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Добавление нового элемента в очередь (push)

tail next

data

new_tail

new_tail = p.get()

next

data

p(new node)

36

Page 37: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Добавление нового элемента в очередь (push)

tail next new_tail

tail->next = std::move(p)

data

next

data

37

Page 38: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Добавление нового элемента в очередь (push)

tail next

data

tail = new_tail

38

Page 39: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

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

void push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value))); std::unique_ptr<node> p(new node); tail->data = new_data; node *const new_tail = p.get(); tail->next = std::move(p); tail = new_tail; }

обращение к tail только на момент

начального сравнения

push обращается

только к tail

try_pop обращается

только к head

39

Page 40: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

Head Tail

▪ Функция push обращается только к tail, try_pop - только к head (и tail на короткое время).

▪ Вместо единого глобального мьютекса можно завести два отдельных и удерживать блокировки при доступке к head и tail.

1 2

40

Page 41: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

template<typename T> class queue {private: struct node { std::shared_ptr<T> data; std::unique_ptr<node> next; };

std::mutex head_mutex, tail_mutex; std::unique_ptr<node> head; node *tail;

node *get_tail() { std::lock_guard<std::mutex> tail_lock(tail_mutex); return tail; }

std::unique_ptr<node> pop_head() { std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == get_tail()) return nullptr; std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }

блокируется только на момент получения элемента tail

41

Page 42: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue &other) = delete; threadsafe_queue &operator=(const threadsafe_queue &other)=delete;

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

void push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value))); std::unique_ptr<node> p(new node); node* const new_tail = p.get(); std::lock_guard<std::mutex> tail_lock(tail_mutex); tail->data = new_data; tail->next = std::move(p); tail = new_tail; }};

push обращается только к tail, но не к head, поэтому используется одна блокировка

42

Page 43: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue &other) = delete; threadsafe_queue &operator=(const threadsafe_queue &other)=delete;

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

void push(T new_value) { node *const old_tail = get_tail(); std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == old_tail) { return nullptr; } std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }};

выполняется не под защитой мьютекса

head_mutex

43

Page 44: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

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

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue &other) = delete; threadsafe_queue &operator=(const threadsafe_queue &other)=delete;

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

void push(T new_value) { node *const old_tail = get_tail(); std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == old_tail) { return nullptr; } std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }};

выполняется не под защитой мьютекса

head_mutex

44

Page 45: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента

Особенности реализации:

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

▪ Проверку условия можно выполнять под защитой head_mutex, захватывая tail_mutex только для чтения tail. Предикат выглядит как head != get_tail()

▪ Для версии pop, работающей со ссылкой, необходимо переопределить wait_and_pop(), чтобы обеспечить безопасность с точки зрения исключений. Необходимо сначала скопировать данные из узла, а потом удалять узел из списка.

45

Page 46: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - объявление класса

template<typename T> class queue {private: struct node { std::shared_ptr<T> data; std::uniquet_ptr<node> next; }; std::mutex head_mutex, tail_mutex; std::unique_ptr<node> head; node *tail; std::condition_variable data_cond;

public: threadsafe_queue(): head(new node), tail(head.get()) {} threadsafe_queue(const threadsafe_queue& other) = delete; std::shared_ptr<T> try_pop(); bool try_pop(T& value); std::shared_ptr<T> wait_and_pop(); void wait_and_pop(T& value); void push(T new_value); void empty(); };

46

Page 47: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - добавление новых значений

template<typename T>void threadsafe_queue<T>::push(T new_value) { std::shared_ptr<T> new_data( std::make_shared<T>(std::move(new_value)));

std::unique_ptr<node> p(new node); { std::lock_guard<std::mutex> tail_lock(tail_mutex); tail->data = new_data; node* const new_tail = p.get(); tail->next = std::move(p); tail = new_tail; }

data_cond.notify_one();}

47

Page 48: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - ожидение и извлечение элемента

template<typename T> class threadsafe_queue {

private: node* get_tail() { std::lock_guard<std::mutex> tail_lock(tail_mutex); return tail; }

std::unique_ptr<node> pop_head() { std::unique_ptr<node> old_head = std::move(head); head = std::move(old_head->next); return old_head; }

std::unique_lock<std::mutex> wait_for_data() { std::unique_lock<std::mutex> head_lock(head_mutex); data_cond.wait(head_lock, [&]{return head.get() != get_tail(); }); return std::move(head_lock); }

Модификация списка в результате удаления головного элемента.

Ожидание появления данных в очередиВозврат объекта блокировки 48

Page 49: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - ожидение и извлечение элемента

std::unique_ptr<node> wait_pop_head() { std::unique_lock<std::mutex> head_lock(wait_for_data()); return pop_head(); }

std::unique_ptr<node> wait_pop_head(T& value) { std::unique_lock<std::mutex> head_lock(wait_for_data()); value = std::move(*head->data); return pop_head(); }

public: std::shared_ptr<T> wait_and_pop() { std::unique_ptr<node> const old_head = wait_pop_head(); return old_head->data; }

void wait_and_pop(T& value) { std::unique_ptr<node> const old_head = wait_pop_head(value); }};

Модификация данных под защитой мьютекса, захваченного в wait_for_data

Модификация данных под защитой мьютекса, захваченного в wait_for_data

49

Page 50: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - try_pop() и empty()

private:

std::unique_ptr<node> try_pop_head() { std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == get_tail()) { return std::unique_ptr<node>(); } return pop_head(); }

std::unique_ptr<node> try_pop_head(T& value) { std::lock_guard<std::mutex> head_lock(head_mutex); if (head.get() == get_tail()) { return std::unique_ptr<node>(); } value = std::move(*head->data); return pop_head(); }

50

Page 51: ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основе блокировок

Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - try_pop() и empty()

public:

std::shared_ptr<T> try_pop() { std::unique_ptr<node> old_head = try_pop_head(); return old_head ? old_head->data : std::shared_ptr<T>(); }

bool try_pop(T& value) { std::unique_ptr<node> const old_head = try_pop_head(value); return old_head; }

void empty() { std::lock_guard<std::mutex> head_lock(head_mutex); return (head.get() == get_tail()); }};

51