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

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

Jul 23, 2015

Download

Education

Alexey Paznikov
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
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