SlideShare a Scribd company logo
1 of 51
Download to read offline
Лекция 6. Разработка
параллельных структур данных
на основе блокировок
Пазников Алексей Александрович
Кафедра вычислительных систем СибГУТИ
Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/
Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring
Параллельные вычислительные технологии
Весна 2015 (Parallel Computing Technologies, PCT 15)
Цель разработки параллельных структур данных
▪ Обеспечить параллельный доступ
▪ Обеспечить безопасность доступа
▪ Минимизировать взаимные исключения
▪ Минимизировать сериализацию
2
Цель разработки параллельных структур данных
Задачи проектирования структур данных с блокировками:
▪ Ни один поток не может увидеть состояние, в котором
инварианты нарушены
▪ Предотвратить состояние гонки
▪ Предусмотреть возникновение исключений
▪ Минимизировать возможность взаимоблокировок
Средства достижения:
▪ ограничить область действия блокировок
▪ защитить разные части структуры разными
мьютексами
▪ обеспечить разный уровень защиты
▪ изменить структуру данных для расширения
возможностей распраллеливания 3
Цель разработки параллельных структур данных
Задачи проектирования структур данных с блокировками:
▪ Ни один поток не может увидеть состояние, в котором
инварианты нарушены
▪ Предотвратить состояние гонки
▪ Предусмотреть возникновение исключений
▪ Минимизировать возможность взаимоблокировок
Средства достижения:
▪ ограничить область действия блокировок
▪ защитить разные части структуры разными
мьютексами
▪ обеспечить разный уровень защиты
▪ изменить структуру данных для расширения
возможностей распраллеливания 4
▪ Инвариант - это состояние структуры, которое должно
быть неизменно при любом обращении к структуре (перед
любой операцией и после каждой операции)
Потокобезопасный стек - потенциальные проблемы
Потенциальные проблемы безопасности реализации
потокобезопасных структур:
1. Гонки данных
2. Взаимные блокировки
3. Безопасность относительно исключений
4. Сериализация
5. Голодание
6. Инверсия приоритетов
7. ...
5
Потокобезопасный стек
struct empty_stack: std::exception { };
template<typename T>
class threadsafe_stack {
private:
std::stack<T> data;
mutable std::mutex m;
public:
threadsafe_stack() {}
threadsafe_stack(const threadsafe_stack &other) {
std::lock_guard<std::mutex> lock(other.m);
data = other.data;
}
threadsafe_stack &operator=(const threadsafe_stack&) = delete;
void push(T new_value) {
std::lock_guard<std::mutex> lock(m);
data.push(std::move(new_value));
}
Защита
данных
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
Потокобезопасный стек - тестовая программа
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
Потокобезопасный стек - безопасность исключений
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
Версия 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
Потокобезопасный стек - взаимоблокировки
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
Потокобезопасный стек - взаимоблокировки
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
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
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
Потокобезопасная очередь с ожиданием
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
Потокобезопасная очередь - тестовая программа
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
Потокобезопасная очередь с ожиданием
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
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
Потокобезопасная очередь - модифицированная версия
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
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
Потокобезопасная очередь - модифицированная версия
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
Очередь с мелкозернистыми блокировками
Head Tail
22
push() pop()
Очередь с мелкозернистыми блокировками
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
Очередь с мелкозернистыми блокировками
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
Очередь с мелкозернистыми блокировками
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
Очередь с мелкозернистыми блокировками
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
Очередь с мелкозернистыми блокировками
Head Tail
next next
27
▪ При пустой очереди head->next и tail->next – есть один
и тот же узел.
▪ В pop и push придётся тогда запирать оба мьютекса. :(
Модифицированная версия
Head Tail
Фиктивный узел
▪ При пустой очереди head и tail указывают на фиктивный
узел, а не равны NULL, причём head == tail.
▪ При очереди с одним элементом head->next и tail->next
указывают на разные узлы (причём head->next == tail), в
результате чего гонки не возникает. 28
Пустая очередь
Head Tail
Фиктивный узел
▪ При пустой очереди head и tail указывают на фиктивный
узел, а не равны NULL, причём head == tail.
29
Очередь с одним элементом
Head Tail
Фиктивный узел
▪ При пустой очереди head и tail указывают на фиктивный
узел, а не равны NULL, причём head == tail.
▪ При очереди с одним элементом head->next и tail-
>next указывают на разные узлы (причём head->next ==
tail), в результате чего гонки не возникает. 30
Очередь с мелкозернистыми блокировками
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
Очередь с мелкозернистыми блокировками
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
Добавление нового элемента в очередь (push)
tail next
data
33
Добавление нового элемента в очередь (push)
tail next
data
p(new node)
34
Добавление нового элемента в очередь (push)
tail next
data
tail->data =
new_data
p(new node)
35
Добавление нового элемента в очередь (push)
tail next
data
new_
tail
new_tail =
p.get()
next
data
p(new node)
36
Добавление нового элемента в очередь (push)
tail next new_
tail
tail->next =
std::move(p)
data
next
data
37
Добавление нового элемента в очередь (push)
tail next
data
tail =
new_tail
38
Очередь с мелкозернистыми блокировками
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
Потокобезопасная очередь с мелкозернистыми блокировками
Head Tail
▪ Функция push обращается только к tail, try_pop -
только к head (и tail на короткое время).
▪ Вместо единого глобального мьютекса можно завести два
отдельных и удерживать блокировки при доступке к head
и tail.
1 2
40
Потокобезопасная очередь с мелкозернистыми блокировками
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
Потокобезопасная очередь с мелкозернистыми блокировками
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
Потокобезопасная очередь с мелкозернистыми блокировками
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
Потокобезопасная очередь с мелкозернистыми блокировками
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
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента
Особенности реализации:
▪ Освободить мьютекс в push до вызова notify_one,
чтобы разбуженный поток не ждал освобождения
мьютекса.
▪ Проверку условия можно выполнять под защитой
head_mutex, захватывая tail_mutex только для
чтения tail. Предикат выглядит как head !=
get_tail()
▪ Для версии pop, работающей со ссылкой,
необходимо переопределить wait_and_pop(),
чтобы обеспечить безопасность с точки зрения
исключений. Необходимо сначала скопировать
данные из узла, а потом удалять узел из списка.
45
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента - объявление класса
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
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента - добавление новых значений
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
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента - ожидение и извлечение элемента
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
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента - ожидение и извлечение элемента
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
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента - 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
Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием
поступления элемента - 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

More Related Content

What's hot

ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...Alexey Paznikov
 
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияAlexey Paznikov
 
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаAlexey Paznikov
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...Alexey Paznikov
 
Модель памяти C++ - Андрей Янковский, Яндекс
Модель памяти C++ - Андрей Янковский, ЯндексМодель памяти C++ - Андрей Янковский, Яндекс
Модель памяти C++ - Андрей Янковский, ЯндексYandex
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиvictor-yastrebov
 
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияДракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияPlatonov Sergey
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерSergey Platonov
 
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияSergey Platonov
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castRoman Orlov
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловSergey Platonov
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksMikhail Kurnosov
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksMikhail Kurnosov
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Yauheni Akhotnikau
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Alexey Paznikov
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о BoostSergey Platonov
 

What's hot (20)

ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
ПВТ - осень 2014 - Лекция 4 - Стандарт POSIX Threads. Реентерабельность. Сигн...
 
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
 
ПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курсаПВТ - весна 2015 - Лекция 0. Описание курса
ПВТ - весна 2015 - Лекция 0. Описание курса
 
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
ПВТ - весна 2015 - Лекция 7. Модель памяти С++. Внеочередное выполнение инстр...
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
Модель памяти C++ - Андрей Янковский, Яндекс
Модель памяти C++ - Андрей Янковский, ЯндексМодель памяти C++ - Андрей Янковский, Яндекс
Модель памяти C++ - Андрей Янковский, Яндекс
 
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
 
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведенияДракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
 
Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптер
 
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизация
 
Догнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_castДогнать и перегнать boost::lexical_cast
Догнать и перегнать boost::lexical_cast
 
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионаловПолухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
Полухин Антон, Как делать не надо: C++ велосипедостроение для профессионалов
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building BlocksЛекция 8: Многопоточное программирование: Intel Threading Building Blocks
Лекция 8: Многопоточное программирование: Intel Threading Building Blocks
 
Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?Для чего мы делали свой акторный фреймворк и что из этого вышло?
Для чего мы делали свой акторный фреймворк и что из этого вышло?
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о Boost
 

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

Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Yandex
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05Computer Science Club
 
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
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаAndrey Karpov
 
Лекция 4: Стек. Очередь
Лекция 4: Стек. ОчередьЛекция 4: Стек. Очередь
Лекция 4: Стек. ОчередьMikhail Kurnosov
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кодаAndrey Karpov
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 
Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Andrey Karpov
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Sergey Platonov
 
Современный статический анализ кода: что умеет он, чего не умели линтеры
Современный статический анализ кода: что умеет он, чего не умели линтерыСовременный статический анализ кода: что умеет он, чего не умели линтеры
Современный статический анализ кода: что умеет он, чего не умели линтерыcorehard_by
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Googleyaevents
 
Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?Andrey Karpov
 
Статический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутСтатический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутAndrey Karpov
 
Всё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программистаВсё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программистаAndrey Karpov
 
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...corehard_by
 
Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Yandex
 
Проблемы 64-битного кода на примерах
Проблемы 64-битного кода на примерахПроблемы 64-битного кода на примерах
Проблемы 64-битного кода на примерахTatyanazaxarova
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийAndrey Akinshin
 

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

Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05
 
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
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кода
 
Лекция 4: Стек. Очередь
Лекция 4: Стек. ОчередьЛекция 4: Стек. Очередь
Лекция 4: Стек. Очередь
 
статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++
 
Современный статический анализ кода: что умеет он, чего не умели линтеры
Современный статический анализ кода: что умеет он, чего не умели линтерыСовременный статический анализ кода: что умеет он, чего не умели линтеры
Современный статический анализ кода: что умеет он, чего не умели линтеры
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Google
 
Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?
 
Статический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минутСтатический анализ: вокруг Java за 60 минут
Статический анализ: вокруг Java за 60 минут
 
SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода
SECON'2014 - Павел Щеваев - Метаданные и автогенерация кодаSECON'2014 - Павел Щеваев - Метаданные и автогенерация кода
SECON'2014 - Павел Щеваев - Метаданные и автогенерация кода
 
Всё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программистаВсё о статическом анализе кода для Java программиста
Всё о статическом анализе кода для Java программиста
 
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
 
Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11Дмитрий Прокопцев — R-ссылки в С++11
Дмитрий Прокопцев — R-ссылки в С++11
 
Progr labrab-4-2013-c++
Progr labrab-4-2013-c++Progr labrab-4-2013-c++
Progr labrab-4-2013-c++
 
Проблемы 64-битного кода на примерах
Проблемы 64-битного кода на примерахПроблемы 64-битного кода на примерах
Проблемы 64-битного кода на примерах
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложений
 

More from 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
 

More from 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 - Лекция 6. Разработка параллельных структур данных на основе блокировок

  • 1. Лекция 6. Разработка параллельных структур данных на основе блокировок Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Q/A: https://piazza.com/sibsutis.ru/spring2015/pct2015spring Параллельные вычислительные технологии Весна 2015 (Parallel Computing Technologies, PCT 15)
  • 2. Цель разработки параллельных структур данных ▪ Обеспечить параллельный доступ ▪ Обеспечить безопасность доступа ▪ Минимизировать взаимные исключения ▪ Минимизировать сериализацию 2
  • 3. Цель разработки параллельных структур данных Задачи проектирования структур данных с блокировками: ▪ Ни один поток не может увидеть состояние, в котором инварианты нарушены ▪ Предотвратить состояние гонки ▪ Предусмотреть возникновение исключений ▪ Минимизировать возможность взаимоблокировок Средства достижения: ▪ ограничить область действия блокировок ▪ защитить разные части структуры разными мьютексами ▪ обеспечить разный уровень защиты ▪ изменить структуру данных для расширения возможностей распраллеливания 3
  • 4. Цель разработки параллельных структур данных Задачи проектирования структур данных с блокировками: ▪ Ни один поток не может увидеть состояние, в котором инварианты нарушены ▪ Предотвратить состояние гонки ▪ Предусмотреть возникновение исключений ▪ Минимизировать возможность взаимоблокировок Средства достижения: ▪ ограничить область действия блокировок ▪ защитить разные части структуры разными мьютексами ▪ обеспечить разный уровень защиты ▪ изменить структуру данных для расширения возможностей распраллеливания 4 ▪ Инвариант - это состояние структуры, которое должно быть неизменно при любом обращении к структуре (перед любой операцией и после каждой операции)
  • 5. Потокобезопасный стек - потенциальные проблемы Потенциальные проблемы безопасности реализации потокобезопасных структур: 1. Гонки данных 2. Взаимные блокировки 3. Безопасность относительно исключений 4. Сериализация 5. Голодание 6. Инверсия приоритетов 7. ... 5
  • 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
  • 7. Потокобезопасный стек 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
  • 8. Потокобезопасный стек - тестовая программа 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
  • 9. Потокобезопасный стек - безопасность исключений 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
  • 10. Версия 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
  • 11. Потокобезопасный стек - взаимоблокировки 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
  • 12. Потокобезопасный стек - взаимоблокировки 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
  • 13. 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
  • 14. 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
  • 15. Потокобезопасная очередь с ожиданием 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
  • 16. Потокобезопасная очередь - тестовая программа 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
  • 17. Потокобезопасная очередь с ожиданием 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
  • 18. 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
  • 19. Потокобезопасная очередь - модифицированная версия 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
  • 20. 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
  • 21. Потокобезопасная очередь - модифицированная версия 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
  • 22. Очередь с мелкозернистыми блокировками Head Tail 22 push() pop()
  • 23. Очередь с мелкозернистыми блокировками 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
  • 24. Очередь с мелкозернистыми блокировками 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
  • 25. Очередь с мелкозернистыми блокировками 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
  • 26. Очередь с мелкозернистыми блокировками 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
  • 27. Очередь с мелкозернистыми блокировками Head Tail next next 27 ▪ При пустой очереди head->next и tail->next – есть один и тот же узел. ▪ В pop и push придётся тогда запирать оба мьютекса. :(
  • 28. Модифицированная версия Head Tail Фиктивный узел ▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail. ▪ При очереди с одним элементом head->next и tail->next указывают на разные узлы (причём head->next == tail), в результате чего гонки не возникает. 28
  • 29. Пустая очередь Head Tail Фиктивный узел ▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail. 29
  • 30. Очередь с одним элементом Head Tail Фиктивный узел ▪ При пустой очереди head и tail указывают на фиктивный узел, а не равны NULL, причём head == tail. ▪ При очереди с одним элементом head->next и tail- >next указывают на разные узлы (причём head->next == tail), в результате чего гонки не возникает. 30
  • 31. Очередь с мелкозернистыми блокировками 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
  • 32. Очередь с мелкозернистыми блокировками 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
  • 33. Добавление нового элемента в очередь (push) tail next data 33
  • 34. Добавление нового элемента в очередь (push) tail next data p(new node) 34
  • 35. Добавление нового элемента в очередь (push) tail next data tail->data = new_data p(new node) 35
  • 36. Добавление нового элемента в очередь (push) tail next data new_ tail new_tail = p.get() next data p(new node) 36
  • 37. Добавление нового элемента в очередь (push) tail next new_ tail tail->next = std::move(p) data next data 37
  • 38. Добавление нового элемента в очередь (push) tail next data tail = new_tail 38
  • 39. Очередь с мелкозернистыми блокировками 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
  • 40. Потокобезопасная очередь с мелкозернистыми блокировками Head Tail ▪ Функция push обращается только к tail, try_pop - только к head (и tail на короткое время). ▪ Вместо единого глобального мьютекса можно завести два отдельных и удерживать блокировки при доступке к head и tail. 1 2 40
  • 41. Потокобезопасная очередь с мелкозернистыми блокировками 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
  • 42. Потокобезопасная очередь с мелкозернистыми блокировками 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
  • 43. Потокобезопасная очередь с мелкозернистыми блокировками 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
  • 44. Потокобезопасная очередь с мелкозернистыми блокировками 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
  • 45. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента Особенности реализации: ▪ Освободить мьютекс в push до вызова notify_one, чтобы разбуженный поток не ждал освобождения мьютекса. ▪ Проверку условия можно выполнять под защитой head_mutex, захватывая tail_mutex только для чтения tail. Предикат выглядит как head != get_tail() ▪ Для версии pop, работающей со ссылкой, необходимо переопределить wait_and_pop(), чтобы обеспечить безопасность с точки зрения исключений. Необходимо сначала скопировать данные из узла, а потом удалять узел из списка. 45
  • 46. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - объявление класса 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
  • 47. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - добавление новых значений 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
  • 48. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - ожидение и извлечение элемента 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
  • 49. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - ожидение и извлечение элемента 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
  • 50. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - 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
  • 51. Потокобезопасная очередь с мелкозернистыми блокировками и с ожиданием поступления элемента - 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