ПВТ - осень 2014 - Лекция 7. Многопоточное...

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

Transcript of ПВТ - осень 2014 - Лекция 7. Многопоточное...

Page 1: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек

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

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

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

Page 2: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Программирование без блокировок

2

Если вы думаете, что программирование без блокировок это просто, значит или вы - один из тех 50, которые умеют

это делать, или же используете атомарные инструкции недостаточно аккуратно.

Герб Саттер

Page 3: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Цели многопоточного программирования без блокировок

3

Однопоточная программа

Многопоточная программа с блокировками

Многопоточная программа без блокировок

Page 4: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Цели многопоточного программирования без блокировок

▪ Повышение масштабируемости путём сокращения блокировок и ожиданий в алгоритмах и структурах данных.

▪ Решение проблем, связанных с блокировками:

▫ Гонки: нужно не забыть заблокировать, причём именно нужный участок кода

▫ Дедлоки: необходимо запирать в нужном порядке различные потоки

▫ Сложность выбора критической секции (простота или масштабируемость)

▫ Голоданеие, инверсия приоритетов и др.4

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

Виды алгоритмов, свободных от блокировок

▪ Свободные от ожиданий (wait-free). “Никто никогда не ждёт”. Каждая операция завершается за N шагов без каких-либо условий. Гарантии:

▫ максимум пропускной способности системы

▫ отсутствие голодания

▪ Свободные от блокировок (lock-free). “Всегда какой-то из потоков работает”. Каждый шаг приближает итоговое решение. Гарантии:

▫ максимум пропускной способности системы

▫ отсутствие голодания (один поток может постоянно ожидать)

▪ Свободные от остановок (obstruction-free). “Поток работает, если нет конфликтов”. За ограниченное число шагов один поток, при условии, что другие потоки остановлены, достигает результата.

▫ Все потоки не блокируются из-за проблем (задержек, ошибок) с другими потоками.

▫ Не гарантируется прогресс, если одновременно работают два или больше потоков. 5

Page 6: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация спинлока на основе атомарного флага

class spinlock_mutex { std::atomic_flag flag;

public: spinlock_mutex(): flag{ATOMIC_FLAG_INIT} { }

void lock() { while (flag.test_and_set( std::memory_order_acquire)); }

void unlock() { flag.clear(std::memory_order_release); }};

n.b. Можно использовать с lock_guard и unique_guard! 6

Page 7: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация модели потребитель-производитель без блокировок

7

Page 8: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель на основе блокировок

8

Производитель

Потребитель

Потребитель

Потребитель

Page 9: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель на основе блокировок

void producer() {

while (ThereAreTasks()) { task = BuildNewTask(); std::unique_lock<std::mutex> lock{mut}; queue.push(task); lock.unlock(); cv.notify(); }

std::unique_lock<std::mutex> lock{mut}; queue.push(done); // добавить признак окончания lock.unlock(); cv.notify();}

9

Page 10: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель на основе блокировок

void consumer() { task = nullptr; while (task != done) { std::unique_lock<std::mutex> lock{mut}; cv.wait(mut, []{ return queue.empty(); }); task = queue.first(); if (task != done) queue.pop(); }

if (task != done) DoWork(task);}

10

Page 11: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

11

is empty?

empty

is empty?

is empty?

empty

is empty?

is empty?

empty

is empty?

is empty?

empty

is empty?

Page 12: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

12

put out

is empty?

is empty?

empty

put out

full

is empty?

empty full

is empty?

is empty?

is empty?

Page 13: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

13

is empty?

is empty?

empty

put in

full

is empty?

empty full

is empty?

process process

is empty?

Page 14: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

14

is empty?

is empty?

put in

full

is empty?

empty full

is empty?

process process

full

is empty?

Page 15: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

15

is empty?

is empty?

full

is empty?

full

is empty?

is empty?

empty fulldone done

put indone flag

put output out

Page 16: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

16

is empty?

full

is empty?

full

is empty?

done donefull

done

put out

empty

is empty?

put indone flag

Page 17: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

17

full

is empty?

full

is empty?

done donefull

done

is empty?

is empty?

donefull

put out

Page 18: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

18

full

is empty?

full

is empty?

done donefull

done

is empty?

is empty?

donefull

Page 19: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

19

Empty Task Done

Start

End

Производитель

Потребитель

Page 20: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель

20

curr = 0; // указатель на текущий слотwhile (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask(); while (slot[curr] != null) // если null, то проверить curr = (curr + 1) % numOfConsumers; // следующий слот slot[curr] = task; sem[curr].signal();}

Page 21: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель

21

curr = 0; // указатель на текущий слотwhile (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask(); while (slot[curr] != null) // если null, то проверить curr = (curr + 1) % numOfConsumers; // следующий слот slot[curr] = task; sem[curr].signal();}

// Фаза 2: выставить флаги “done” во всех слотахnumNotified = 0;while (numNotified < numOfConsumers) { while (slot[curr] != null) // если null, то проверить curr = (curr + 1) % numOfConsumers; // следующий slot[curr] = done; // освободить слот sem[curr].signal(); ++numNotified;}

Page 22: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Потребитель

22

task = null;while (task != done)

// Дождаться, когда слот будет полным while ((task = slot[mySlot]) == null) sem[mySlot].wait();

if (task != done) { slot[mySlot] = null; // помечаем слот пустым DoWork(task); // выполняем задачу } // вне критической секции}

Page 23: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Потребитель

23

task = null;while (task != done)

// Дождаться, когда слот будет полным while ((task = slot[mySlot]) == null) sem[mySlot].wait();

if (task != done) { slot[mySlot] = null; // помечаем слот пустым DoWork(task); // выполняем задачу } // вне критической секции}

Как применить модель памяти С++?

Page 24: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель, модель памяти С++

24

curr = 0; while (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask();

while (slot[curr] != null) // acquire null curr = (curr + 1) % numOfConsumers; slot[curr] = task; // release non-null sem[curr].signal();}// Фаза 2: выставить флаги “done” во всех слотахnumNotified = 0;while (numNotified < numOfConsumers) { while (slot[curr] != null) // acquire null curr = (curr + 1) % numOfConsumers; slot[curr] = done; // release done sem[curr].signal(); ++numNotified;}

Page 25: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Птребитель, модель памяти С++

25

task = null;while (task != done)

// Дождаться, когда слот будет полным while ((task = slot[mySlot]) // acquire non-null == null) sem[mySlot].wait();

if (task != done) { slot[mySlot] = null; // release null DoWork(task); } }

Page 26: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель, класс алгоритма

26

curr = 0; while (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask();

while (slot[curr] != null) // acquire null curr = (curr + 1) % numOfConsumers; slot[curr] = task; // release non-null sem[curr].signal();}// Фаза 2: выставить флаги “done” во всех слотахnumNotified = 0;while (numNotified < numOfConsumers) { while (slot[curr] != null) // acquire null curr = (curr + 1) % numOfConsumers; slot[curr] = done; // release done sem[curr].signal(); ++numNotified;}

Алгоритм - свободный от ожиданий, свободный от блокировок или свободный от остановок?

Page 27: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель, класс алгоритма

27

curr = 0; while (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask();

while (slot[curr] != null) // acquire null curr = (curr + 1) % numOfConsumers; slot[curr] = task; // release non-null sem[curr].signal();}// Фаза 2: выставить флаги “done” во всех слотахnumNotified = 0;while (numNotified < numOfConsumers) { while (slot[curr] != null) // acquire null curr = (curr + 1) % numOfConsumers; slot[curr] = done; // release done sem[curr].signal(); ++numNotified;}

Алгоритм - свободный от ожиданий, свободный от блокировок или свободный от остановок?

Этап 2:Свободная от остановок

Этап 1:Свободный от ожиданий

Page 28: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Производитель-потребитель без блокировок

28

task = null;while (task != done)

// Дождаться, когда слот будет полным while ((task = slot[mySlot]) == null) sem[mySlot].wait();

if (task != done) { slot[mySlot] = null; DoWork(task); } }

можно ли поменять две строки?нужно ли это сделать?

Page 29: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация стека, свободного от блокировок, на основе сборщика мусора

29

Page 30: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Стек, свободный от блокировок

30

T T T T

head

1. Конструктор

2. Деструктор

3. Поиск узла (find)

4. Добавление узла (push)

5. Удаление узла (pop)

Page 31: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Стек, свободный от блокировок

31

template <typename T>class lfstack {public: lfstack(); ~lfstack(); T* find(T data) const; // найти элемент, равный data void push(T data); // добавить элемент в голову

private: struct node { // атомарные операции T data; // не требуются node* next; }; std::atomic<node*> head{nullptr}; // атомарный указатель}; // на голову списка

Page 32: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Конструктор и деструктор

32

template <typename T>lfstack<T>::lfstack() {}

Объект создаётся в одном потоке, поэтому не нужно обеспечивать параллельный доступ. Нельзя использовать стек до тех пор, пока он не будет создан, т.е. до выполнения конструктора, и после того, как он будет уничтожен, т.е. после выполнения деструктора.

template <typename T>lfstack<T>::~lfstack() { auto first = head.load(); while (first) { auto unlinked = first; first = first->next; delete unlinked; }}

Page 33: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция push

33

1. Создать новый узел.

2. Записать в его указатель next текущее значение head.

3. Записать в head указатель на новый узел.

void push(T const& data) { auto new_node = new node{data}; // (1) node_node->next = head.load(); // (2) head = new_node; // (3)}

struct node { T data; node* next; node(T const& _data): data{_data} {}};

Page 34: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция push

34

T

head

TTT

Начальная стадия

Промежуточная стадия

T

head

TTT

T

Конечная стадия

T

head

TTT

T

Page 35: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция push

35

T

head

TTT

Начальная стадия

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

T

head

TTT

T

Первый добавляемый элемент пропал, остался только второй

T

head

TTT

T

T T

Page 36: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция push

36

void push(T const& data) { auto new_node = new node{data}; // (1) node_node->next = head.load(); // (2) head = new_node; // (3) while (!head.compare_exchange_weak(new_node->next, new_node)); // (3)}

1. Создать новый узел.

2. Записать в его указатель next текущее значение head.

3. Записать в head указатель на новый узел, при этом с помощью операции сравнить-и-обменять гарантировать то, что head не был модифицирован с момента чтения на шаге 2.

Page 37: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (ошибочная)

37

void pop(T& result) { node* old_head = head.load(); head = head->next; result = old_head->data; }

Page 38: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop

38

T

head

TTT

Начальная стадия

Промежуточная стадия

T

head

TTT

Конечная стадия

T

head

TT

Page 39: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop

39

T

head

TTT

Начальная стадия

Промежуточная стадия

T

head

TTT

Конечная стадия

T

head

TT

B

A

A B

Page 40: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (ошибочная)

40

void pop(T& result) { node* old_head = head.load();

while (!head.compare_exchange_weak(old_head, old_head->next); result = old_head->data; }

Page 41: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (ошибочная)

41

void pop(T& result) { node* old_head = head.load();

while (!head.compare_exchange_weak(old_head, old_head->next); result = old_head->data; }

Page 42: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (ошибочная)

42

void pop(T& result) { node* old_head = head.load();

while (!head.compare_exchange_weak(old_head, old_head->next); result = old_head->data; }

std::shared_ptr<T> pop(T& result) { node* old_head = head.load(); while (old_head && !head.compare_exchange_weak(old_head, old_head->next)); return old_head ? old_head->data : std::shared_ptr<T>(); }

Page 43: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (ошибочная)

43

class lfstack {private: struct node { std::shared_ptr<T> data; node* next; node(T const& _data): data(std::make_shared<T>(_data)) { } };

...

std::shared_ptr<T> pop(T& result) { node* old_head = head.load(); while (old_head && !head.compare_exchange_weak(old_head, old_head->next)); return old_head ? old_head->data : std::shared_ptr<T>(); }

Page 44: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Проблема АВА

44

4

old_head

321

head->nextПоток А выполняет удаление узла

из вершины стека

Page 45: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Проблема АВА

45

4

old_head

321

head->next

43

Поток А выполняет удаление узла из вершины стека

old_head old_head->nextПоток А был вытеснен и другие

потоки удалили два узла из стека

Page 46: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Проблема АВА

46

4

old_head

321

head->next

43

Поток А был вытеснен и другие потоки удалили два узла из стека

Поток А выполняет удаление узла из вершины стека

435

old_head old_head->next

old_head old_head->nextНекий поток добавил новый узел и

аллокатор выделил под него ту же память

Page 47: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Проблема АВА

47

4

old_head

321

head->next

43

Поток А выполняет удаление узла из вершины стека

43

Некий поток добавил новый узел и аллокатор выделил под него ту же память

5

43

Поток А: head.compare_exchange( old_head, old_head->next))

5

old_head old_head->next

old_head old_head->next

old_head old_head->next

Поток А был вытеснен и другие потоки удалили два узла из стека

Page 48: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Проблема АВА

48

4

old_head

321

head->next

43

Поток А выполняет удаление узла из вершины стека

435

43

Некий поток добавил новый узел и аллокатор выделил под него ту же память

Поток А: head.compare_exchange( old_head, old_head->next))

5

old_head old_head->next

old_head old_head->next

old_head old_head->next

Поток А был вытеснен и другие потоки удалили два узла из стека

Page 49: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Решения проблемы АВА

49

1. “Ленивый” сборщик мусора

2. Указатели опасности

3. Счётчик ссылок на элемент

4. Сделать узлы уникальными

5. Вообще не удалять узлы

6. Добавление дополнительных узлов

7. и т.д.

Page 50: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (наивная)

50

// количество потоков, выполняющих popstd::atomic<unsigned> threads_in_pop;

std::shared_ptr<T> pop() { threads_in_pop++;

node* old_head = head_load(); while (old_head && !head.compare_exchange_weak(old_head, old_head->next)); std::shared_ptr<T> res; if (old_head) res.swap(old_head->data); // не копировать, // а обменять данные

try_reclaim(old_head); // попробовать освободить // удалённые узлы return res;}

Page 51: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop (наивная)

51

template<typename T>class lfstack {private: std::atomic<node*> delete_list;

static void delete_nodes(node* nodes); while (nodes) { node* next = nodes->next; delete nodes; nodes = next; } }

Page 52: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция try_reclaim освобождения удалённых узлов

52

void try_reclaim(node* old_head) { if (threads_in_pop == 1) { // я единственный в pop?

node* nodes_to_delete = // захватить список delete_list.exchange{nullptr}; // на удаление

if (!--thread_in_pop) // точно единственный? delete_nodes(nodes_to_delete)); // удалить всё! else if (nodes_to_delete) // если в захваченном списке // что-то было // вернуть это в общий список узлов на удаление chain_pending_nodes(nodes_to_delete);

delete old_head; // удаляем хотя бы только что // исключённый узел } else { // удалим узел как-нибудь потом chain_pending_node(old_head); --threads_in_pop; }}

Page 53: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция try_reclaim освобождения удалённых узлов

53

// добавляем захваченный список в общий список узлов,// подлежащих удалениюvoid chain_pending_nodes(node* nodes) { node* last = nodes; while (node* const next = last->next) last = next; chain_pending_nodes(nodes, last);}

// добавить список узлов в список узлов на удалениеvoid chain_pending_nodes(node* first, node* last) { last->next = delete_list; while (!delete_list.compare_exchange_weak(last->next, first));}

// добавить узел в список узлов на удалениеvoid chain_pending_node(node* n) { chain_pending_nodes(n, n);}

Page 54: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция try_reclaim освобождения удалённых узлов

54

4

head

321

delete_list 0threads_in_pop

4

head

321

delete_list

threads_in_pop 1

5

5A A удаляет узел 1 и

вытесняется в pop() после 1-го чтения threads_in_pop

Page 55: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция try_reclaim освобождения удалённых узлов

55

4

head

32

delete_list 2threads_in_pop

43

delete_list

threads_in_pop 2

5

2

С удаляет узел и продолжает работать до момента выхода из pop()

old_head

BB вызывает pop() и

вытесняется после 1-го чтения head

A

headold_head

B4

C

5

A

Page 56: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция try_reclaim освобождения удалённых узлов

56threads_in_pop 2

43

delete_list

threads_in_pop 2

2

A возобновляет выполнение и захватывает список на удаление. После этого он должен 2-й раз проверить,

один ли он в pop()

headold_head

B2

5

A

43

headold_head

B2

delete_list A2 5

delete_list

B возобновляет выполнение, выполняет CAS и переходит

к 3 узлу

Page 57: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация стека, свободного от блокировок, на основе указателей опасности

57

Page 58: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

58

4

old_head

321

head->next

43

Поток А выполняет удаление узла из вершины стека и помечает узел 1

как узел, который он использует.

old_head old_head->next

Поток А был вытеснен и другие потоки удалили два узла из стека, но не

освобождают память из-под первого узла.

2

head

head

1

A “понимает”, что головной узел head изменился и нужно

выполнить compare_exchange

43

old_head old_head->next

21

Page 59: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop на основе указателей опасности

59

std::shared_ptr<T> pop() { std::atomic<void*>& hp = get_hazard_pointer_for_current_thread();

// установить указатель опасности перед чтением указателя, // который мы собираемся разыменовывать node* old_head = head.load(); node* temp; do { temp = old_head; hp.store(old_head); old_head = head.load(); } while (old_head != temp); // ...}

Page 60: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop на основе указателей опасности

60

std::shared_ptr<T> pop() { std::atomic<void*>& hp = get_hazard_pointer_for_current_thread();

node* old_head = head.load(); do { node* temp; do { temp = old_head; hp.store(old_head); // устанавливаем УО old_head = head.load(); } while (old_head != temp); } while (old_head && // получаем узел !head.compare_exchange_strong(old_head, old_head->next)); hp.store(nullptr);

Page 61: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

61

4

old_head

321head

hp

Page 62: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

62

4

old_head

321head

temp = old_head

temp

hp

Page 63: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

63

4

old_head

321head

hp

temp = old_head

temp

hp.store(old_head)

Page 64: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

64

4

old_head

321

temp = old_head

head

temp

hp.store(old_head)

old_head = head.load()“old old_head”

“new old_head”

hp

Page 65: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

65

4

old_head

321

temp = old_head

head

temp

hp.store(old_head)

old_head = head.load()“old old_head”

“new old_head”

hp

== ?

Таким образом, внутренний цикл гарарантирует то, что указатель опасности будет указывать на тот головной элемент head, с котором

мы будем работать (сдвигать указатель на следующий элемент)

Проверка позволяет определить, не изменился ли головной элемент с тех пор, когда мы запомнили его в указателе опасности.

Page 66: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

66

4

old_head

321

temp = old_head

head

temp

hp.store(old_head)

old_head = head.load()“old old_head”

“new old_head”

hp

== ?

Во внешнем цикле сдвигаем указатель с head на следующий элемент с уверенностью, что никто не подменит элемент head.

Page 67: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Указатели опасности (hazard pointers)

67

После того, как поток А успешно выполнил compare_exchange,

указатель опасности можно обнулять hp.store(nullptr), т.к. никто пока

не сможет удалить old_head, кроме А, поскольку head изменён потоком А

43

old_head old_head->next

21

Вариант 1

43

old_head->next

1

Вариант 2

old_head

2

Page 68: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Функция pop на основе указателей опасности

68

std::shared_ptr<T> res; if (old_head) { res.swap(old_head->data); // извлекаем данные

if (outstanding_hazard_pointers_for(old_head)) // если опасно, удаляем потом reclaim_later(old_head); else // если не опасно, удаляем сейчас delete old_head;

// пробуем удалить узлы, какие можно удалить delete_nodes_with_no_hazards(); } return res;}

Page 69: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

69

4321head

1 5 6 7 m432

max_hazard_pointers

Page 70: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

70

4321head

1 5 6 7 m432

Указатели опасности, m = max_hazard_pointers

пустой?нет

if (hazard_pointers[i].id. compare_exchange_strong( old_id, std::this_thread::get_id()))

thread_local hp

Page 71: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

71

4321head

1 5 6 7 m432

Указатели опасности, m = max_hazard_pointers

пустой?да

if (hazard_pointers[i].id. compare_exchange_strong( old_id, std::this_thread::get_id()))

thread_local hp

Page 72: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

72

4321head

1 5 6 7 m432

Указатели опасности, m = max_hazard_pointers

пустой?да

if (hazard_pointers[i].id. compare_exchange_strong( old_id, std::this_thread::get_id()))

thread_local hp

Page 73: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

73

const auto max_hazard_pointers = 100;

struct hazard_pointer { std::atomic<std::thread::id> id; std::atomic<void*> pointer;};

hazard_pointer hazard_pointers[max_hazard_pointers];

class hp_owner { hazard_pointer* hp;

public: hp_owner(hp_owner const&) = delete; hp_owner operator=(hp_owner const&) = delete;

Page 74: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

74

hp_owner(): hp{nullptr} { for (auto i = 0; i < max_hazard_pointers; i++) { std::thread::id old_id; // пустой незанятый УО

// если i-й УО не занят, завладеть им, записав в него // свой идентификатор потока if (hazard_pointers[i].id.compare_exchange_strong( old_id, std::this_thread::get_id())) { hp = &hazard_pointers[i]; // я владею i-м УО break; } }

// таблица УО закончилась, указателей нам не досталось if (!hp) throw std::runtime_error("No hazard ptrs available");}

Page 75: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

75

hp_owner(): hp{nullptr} { for (auto i = 0; i < max_hazard_pointers; i++) { std::thread::id old_id; if (hazard_pointers[i].id.compare_exchange_strong( old_id, std::this_thread::get_id())) { hp = &hazard_pointers[i]; break; } } if (!hp) throw std::runtime_error("No hazard ptrs available");}std::atomic<void*>& get_pointer() { return hp->pointer;}~hp_owner() { hp->pointer.store(nullptr); hp->id.store(std::thread::id());}

Page 76: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

76

// вернуть указатель опасности для текущего потокаstd::atomic<void*>& get_hazard_pointer_for_current_thread() { thread_local static hp_owner hazard; return hazard.get_pointer();}

Page 77: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация указателей опасности

77

// вернуть указатель опасности для текущего потокаstd::atomic<void*>& get_hazard_pointer_for_current_thread() { thread_local static hp_owner hazard; return hazard.get_pointer();}

// проверить, не ссылается ли на указатель какой-то из УОbool outstanding_hazard_pointers_for(void* p) { for (auto i = 0; i < max_hazard_pointers; i++) { if (hazard_pointers[i].pointer.load() == p) { return true; } } return false;}

Page 78: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

78

template <typename T>void do_delete(void* p) { delete static_cast<T*>(p);}

struct data_to_reclaim { // обёртка над данными для

void* data; // помещения в список удаления

std::function<void(void*)> deleter;

data_to_reclaim* next;

template<typename T> data_to_reclaim(T* p): data{p}, deleter{&do_delete<T>}, next{0} { }

~data_to_reclaim() { deleter(data); }};

std::atomic<data_to_reclaim*> nodes_to_reclaim;

Page 79: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

79

// добавить элемент в список на удалениеvoid add_to_reclaim_list(data_to_reclaim* node) { node->next = nodes_to_reclaim.load();

while (!nodes_to_reclaim.compare_exchange_weak( node->next, node));}

// удалить элемент позжеtemplate<typename T>void reclaim_later(T* data) { add_to_recalim_list(new data_to_reclaim(data));}

Page 80: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

80

void delete_nodes_with_no_hazards() { // захватить текущий список data_to_reclaim* current = nodes_to_reclaim.exchange(nullptr);

while (current) { data_to_reclaim* const next = current->next;

if (!outstanding_hazard_pointers_for(current->data)) // если не опасно, удалить сейчас delete current; else // если опасно удалить потом add_to_reclaim_list(current);

current = next; }}

Page 81: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

81

4321

nodes_to_reclaim

Page 82: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

82

nodes_to_reclaim

4321current

current = nodes_to_reclaim. exchange(nullptr);

Page 83: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

83

nodes_to_reclaim

432current

1

1

add_to_reclaim_list(current);

Page 84: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

84

nodes_to_reclaim

432current

1

delete current;

Page 85: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

85

nodes_to_reclaim

43current

1 5

add_to_reclaim_list() при выполнении pop()

Page 86: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

86

nodes_to_reclaim

4current

1 5 3

add_to_reclaim_list(current);

1

Page 87: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

87

nodes_to_reclaim

4current

1 5 3

Page 88: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация функции освобождения памяти

88

nodes_to_reclaim

current

1 5 3

Page 89: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Недостатки указетелей опасности

89

1. Просмотр массива указателей опаности требует в худшем случае max_hazard_pointers атомарных переменных.

2. Атомарные операции могут работать медленнее эквивалентных обычных операций

3. При освобождении узла также требуется просмотреть список указателей опаности, т.е. max_hazard_pointers в худшем случае.

Функция pop дорогостоящая. Решения?

Page 90: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Недостатки указетелей опасности

90

1. Просмотр массива указателей опаности требует в худшем случае max_hazard_pointers атомарных переменных.

2. Атомарные операции могут работать медленнее эквивалентных обычных операций

3. При освобождении узла также требуется просмотреть список указателей опаности, т.е. max_hazard_pointers в худшем случае.

Функция pop дорогостоящая. Решения?

1. Вместо просмотра max_hazard_pointers в каждом pop(), проверяем 2 * max_hazard_pointers через каждые max_hazard_pointers вызовов pop() и освобождаем не менее max_hazard_pointers. В среднем проверяем два узла при каждом вызове pop() и один освобождаем.

2. Каждый поток хранит собственный список освобождения в локальных данных потока. Это потребует выделения памяти под max_hazard_pointers2 узлов.

Page 91: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация стека, свободного от блокировок, с помощью умного указателя

91

Page 92: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация на основе атомарного умного указателя

92

▪ Удалять узлы можно только при отсутствии обращения к ним из других потоков

▪ Если на узел нет ссылки, то его можно удаляь

Page 93: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация на основе атомарного умного указателя

93

▪ Удалять узлы можно только при отсутствии обращения к ним из других потоков

▪ Если на узел нет ссылки, то его можно удаляь

▪ Умный указатель shared_ptr как раз решает эту задачу!

Page 94: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация на основе атомарного умного указателя

94

▪ Удалять узлы можно только при отсутствии обращения к ним из других потоков

▪ Если на узел нет ссылки, то его можно удаляь

▪ Умный указатель shared_ptr как раз решает эту задачу!

...

▪ Но, к сожалению, атомарные операции shared_ptr в большинстве реализаций не свободны от блокировок.

Page 95: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация на основе атомарного умного указателя

95

template <typename T>class lfstack {private:

struct node { std::shared_ptr<T> data; std::shared_ptr<node> next; node(T const& _data): data(std::make_shared<T>(_data)) { } };

std::shared_ptr<node> head;

Page 96: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация на основе атомарного умного указателя

96

...

void push(T const& data) { std::shared_ptr<node> const new_node = std::make_shared<node>(data); new_node->next = head.load();

while (!std::atomic_compare_exchange_weak(&head, &nead_node->next, new_node)); }

std::shared_ptr<T> pop() { std::shared_ptr<node> old_head = std::atomic_load(&head);

while (old_head && !std::atomic_compare_exchange_weak(&head, &old_head, old_head->next));

return old_head ? old_head->data : std::shared_ptr<T>(); }

Page 97: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Реализация стека, свободного от блокировок, с помощью подсчёта ссылок

97

Page 98: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Двойной счётчик ссылок

98

counted_node_ptr

node

internal_count

data

next

external_count

▪ При начале каждого чтения внешний счётчик увеличивается.

▪ При завершении чтения внутренний счётчик уменьшается.

▪ При удалении узла внутренний счетчик увеличивается на величину внешнего минус 1, а внешний отбрасывается.

▪ Если внутренний счётчик равен 0, узел можно удалять.

Page 99: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Двойной счётчик ссылок

99

head

1 2 3

Page 100: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Двойной счётчик ссылок

100

template<typename T>class lfstack {private:

struct counted_node_ptr { int external_count; node* ptr; };

struct node { std::shared_ptr<T> data; std::atomic<int> internal_count; counted_node_ptr next; node(T const& _data): data(std::make_shared<T>(_data)), internal_count(0) {} };

std::atomic<counted_node_ptr> head;

Page 101: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Двойной счётчик ссылок

101

~lfstack() { while (pop()); }

void push(T const& data) { counted_node_ptr new_node; new_node.ptr = new node(data); new_node.external_count = 1; new_node.ptr->next = head.load(); while (!head.compare_exchange_weak(new_node.ptr->next, new_node)); }};

Page 102: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Двойной счётчик ссылок

102

template <typename T>class lfstack {private:

// Увеличение внешнего счётчика void increase_head_count(counted_node_ptr& old_counter) { counted_node_ptr new_counter;

do { new_counter = old_counter; ++new_counter.external_count; } while (!head.compare_exchange_strong(old_counter, new_counter));

old_counter.external_count = new_counter.external_count; }

Page 103: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

public: std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load();

for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; } }

Двойной счётчик ссылок

103

Page 104: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

public: std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load();

for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; } }

Двойной счётчик ссылок

104

1. Увеличить внешний счётчик2. Разыменовать указатель3. Проверить указатель на пустоту

Page 105: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

public: std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load();

for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; } }

Двойной счётчик ссылок

105

Попытаться выполнить удаление узла

1. Если получилось, забрать данные2. Прибавить внутренний счётчик к внешнему3. Если счётчик стал равным 0, удалить узел4. Вернуть результат (даже если счётчик не стал равным 0)

Page 106: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

public: std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load();

for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; } }

Двойной счётчик ссылок

106

Если не получилось выполнить удаление узла (какой-то поток удалил узел раньше нас)1. Уменьшить счётчик ссылок на 1.2. Если другие потоки на узел не ссылаются, освободить память

(убрать за тем потоком, который выполнил удаление)

Page 107: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0

Двойной счётчик ссылок

107

1

0

1 1

head

1 2 3

Сценарий 1:

Поток А эксклюзивно удаляет узел.

Другие потоки ему не мешают.

Page 108: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0

Двойной счётчик ссылок

108

2 1 1

head

1 2 3

Поток A:increase_head_count(old_head)node* const = old_head.ptr

0

Page 109: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

109

2 1 1

head

1 2 3

Поток A:head.compare_exchange(old_head, ptr->next)

Page 110: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

110

2 0 0

head

1 2 3

Поток A:count_increase = 2 - 2 = 0internal_count = 0 + 0 = 0

Page 111: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

111

2 0 0

head

1 2 3

Поток A:delete ptr

Page 112: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

112

1 1 1

head

1 2 3

Сценарий 2:

Потоки А и В одновременно удаляют узел.

Потоку А удаётся выполнить удаление узла вперёд B.

Поток В успевает выйти из pop до того,

как А попробует освободить узел.

Page 113: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

113

2 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

Page 114: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

114

3 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Page 115: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

-1 0 0

Двойной счётчик ссылок

115

3 0 0

head

1 2 3

Поток B: increase_head_count(old_head)

Поток B: internal_count.fetch_sub(1)

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Page 116: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

116

x 0 0

head

1 2 3

Поток A: count_increase = 3 - 2 = 1 internal_count = -1 + 1 = 0

Поток B: increase_head_count(old_head)

Поток B: internal_count = 0 - 1 = -1

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Page 117: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

117

x 0 0

head

1 2 3

Поток A: count_increase = 3 - 2 = 1 internal_count = -1 + 1 = 0

Поток B: increase_head_count(old_head)

Поток B: internal_count = 0 - 1 = -1

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Поток A: delete ptr

Page 118: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

118

1 1 1

head

1 2 3

Сценарий 3:

Потоки А и В одновременно удаляют узел.

Потоку А удаётся выполнить удаление узла вперёд B.

Поток В не успевает выйти из pop, когда А пытается

освободить узел, и поэтому А узел не освобождает.

Зато поток В, последним покидая узел,

освобождает память из-под узла, удалённого А.

Page 119: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

119

3 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Page 120: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

1 0 0

Двойной счётчик ссылок

120

x 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Поток A: count_increase = 3 - 2 = 1 internal_count = 0 + 1 = 1

Поток A узел не освобождает

Page 121: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

0 0 0

Двойной счётчик ссылок

121

x 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

Поток A: increase_head_count(old_head) head.compare_exchange(...)

Поток A: count_increase = 3 - 2 = 1 internal_count = 0 + 1 = 1

Поток A узел не освобождает

Поток B: internal_count = 1 - 1 = 0 delete ptr

Узел освобождает поток B

Page 122: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Двойной счётчик ссылок - проблема

122

template<typename T>class lfstack {private:

struct counted_node_ptr { int external_count; node* ptr; };

struct node { std::shared_ptr<T> data; std::atomic<int> internal_count; counted_node_ptr next; node(T const& _data): data(std::make_shared<T>(_data)), internal_count(0) {} };

std::atomic<counted_node_ptr> head;

Структура может не поддерживать выполнение атомарных операций без блокировок!

Page 123: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++ для стека, свободного от блокировок

123

Page 124: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

124

void push(T const& data) { counted_node_ptr new_node; new_node.ptr = new node(data); new_node.external_count = 1; new_node.ptr->next = head.load(std::memory_order_relaxed);

while (!head.compare_exchange_weak(new_node.ptr->next, new_node));

void increase_head_count(counted_node_ptr& old_counter) { counted_node_ptr new_counter;

do { new_counter = old_counter; ++new_counter.external_count; } while (!head.compare_exchange_strong(old_counter, new_counter));

old_counter.external_count = new_counter.external_count;}

Page 125: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

125

void push(T const& data) { counted_node_ptr new_node; new_node.ptr = new node(data); new_node.external_count = 1; new_node.ptr->next = head.load(std::memory_order_relaxed);

while (!head.compare_exchange_weak(new_node.ptr->next, new_node));

void increase_head_count(counted_node_ptr& old_counter) { counted_node_ptr new_counter;

do { new_counter = old_counter; ++new_counter.external_count; } while (!head.compare_exchange_strong(old_counter, new_counter));

old_counter.external_count = new_counter.external_count;}

Подготовка данных

Установка head (“флага”)

Проверка head (“флага”)

Работа с добавленным элементом

Page 126: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

126

void push(T const& data) { counted_node_ptr new_node; new_node.ptr = new node(data); new_node.external_count = 1; new_node.ptr->next = head.load(std::memory_order_relaxed);

while (!head.compare_exchange_weak(new_node.ptr->next, new_node, std::memory_order_release, std::memory_order_relaxed));

void increase_head_count(counted_node_ptr& old_counter) { counted_node_ptr new_counter;

do { new_counter = old_counter; ++new_counter.external_count; } while (!head.compare_exchange_strong(old_counter, std::memory_order_acquire, std::memory_order_relaxed, new_counter));

old_counter.external_count = new_counter.external_count;}

Подготовка данных

Установка head (“флага”)

Работа с добавленным элементом

Проверка head (“флага”)

Page 127: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

127

std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load(); for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; }}

Чтение указателя

Page 128: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

128

std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load(); for (;;) { increase_head_count(old_head);

node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next, std::memory_order_relaxed)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; }}

Чтение указателя

acquire не нужен, т.к. захват выполнен в

increase_head_count

Page 129: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

129

std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load(); for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next, std::memory_order_relaxed)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; }}

Извлечение данных

Удаление должно выполняться после извлечения данных

Page 130: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

130

std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load(); for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next, std::memory_order_relaxed)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase, std::memory_order_release) == -count_increase) delete ptr; return res;

} else if (ptr->internal_count.fetch_sub(1) == 1) delete ptr; }}

Удаление должно выполняться после извлечения данных

Извлечение данных

Page 131: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

131

std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load(); for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next, std::memory_order_relaxed)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase, std::memory_order_release) == -count_increase) delete ptr; return res; } else if (ptr->internal_count.fetch_sub(1, std::memory_order_acquire) == 1)

delete ptr; }

Удаление должно выполняться после извлечения данных

Извлечение данных

Page 132: ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель.

Применение модели памяти С++

132

std::shared_ptr<T> pop() { counted_node_ptr old_head = head.load(); for (;;) { increase_head_count(old_head); node* const ptr = old_head.ptr; if (!ptr) return std::shared_ptr<T>();

if (head.compare_exchange_strong(old_head, ptr->next, std::memory_order_relaxed)) { std::shared_ptr<T> res; res.swap(ptr->data); int const count_increase = old_head.external_count - 2;

if (ptr->internal_count.fetch_add(count_increase, std::memory_order_release) == -count_increase) delete ptr; return res; } else if (ptr->internal_count.fetch_sub(1, std::memory_order_relaxed) == 1) ptr->internal_count.load(std::memory_order_acquire);

delete ptr; }

Достаточно вставить операцию захвата-загрузки, чтобы удалить ptr после извлечения

данных

Извлечение данных

fetch_sub входит в последовательность

освобождений, поэтому “не мешает” acquire