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

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

Jul 23, 2015

Download

Education

Alexey Paznikov
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

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

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

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

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

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

2

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

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

Герб Саттер

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

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

3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5

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

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

6

?

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

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

7

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

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

8

t ≥ 30 мин.■ 40 мин.■ 1 час■ 2 часа■ ...

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

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

9

t ≥ 30 мин.■ 40 мин.■ 1 час■ 2 часа■ ...

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

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

10

t ≥ 30 мин.■ 40 мин.■ 1 час■ 2 часа■ ...

t = 1 час ± 10 мин.

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

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

11

locks (с блокировками)

lock-free (без блокировок)

t ≥ 30 мин.■ 40 мин.■ 1 час■ 2 часа■ ...

t = 1 час ± 10 мин.

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

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

12

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

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

13

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

Потребитель

Потребитель

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

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

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

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

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();}

14

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

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

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);}

15

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

1 производитель, K потребителей

16

emptyempty empty empty

1 2 3 K

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

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

17

is empty?

empty

is empty?

is empty?

empty

is empty?

is empty?

empty

is empty?

is empty?

empty

is empty?

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

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

18

put out

is empty?

is empty?

empty

put out

full

is empty?

empty full

is empty?

is empty?

is empty?

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

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

19

is empty?

is empty?

empty

put in

full

is empty?

empty full

is empty?

process process

is empty?

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

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

20

is empty?

is empty?

put in

full

is empty?

empty full

is empty?

process process

full

is empty?

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

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

21

is empty?

is empty?

full

is empty?

full

is empty?

is empty?

empty fulldone done

put indone flag

put output out

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

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

22

is empty?

full

is empty?

full

is empty?

done donefull

done

put out

empty

is empty?

put indone flag

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

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

23

full

is empty?

full

is empty?

done donefull

done

is empty?

is empty?

donefull

put out

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

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

24

full

is empty?

full

is empty?

done donefull

done

is empty?

is empty?

donefull

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

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

25

Empty Task Done

Start

End

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

Потребитель

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

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

26

curr = 0; // указатель на текущий слотwhile (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask();

while (slot[curr] != null) // если null, то проверить curr = (curr + 1) % numOfConsumers; // следующий слот

slot[curr] = task; sem[curr].signal();}

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

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

27

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 28: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

Потребитель

28

task = null;while (task != done)

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

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

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

Потребитель

29

task = null;while (task != done)

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

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

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

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

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

30

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 31: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

31

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 32: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

32

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 33: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

33

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 34: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

34

task = null;while (task != done)

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

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

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

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

N производителей, K потребителей

35

1 2 3 K

? ? ? ?

? ? ? ?

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

N производителей, K потребителей

36

1 2 3 K

? ? ? ?

? ? ? ?

гонка!

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

N производителей, K потребителей

37

is empty?

is empty?

is empty?

is empty?

1 2 3 K

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

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

N производителей, K потребителей

38

is empty?

is empty?

is empty?

is empty?

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

1 2 3 K

CAS – compare and swap (compare exchange)

CAS – атомарное сравнение с обменом

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

N производителей, K потребителей

39

producer (вариант):

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

while (!slot[curr].CAS(null, task)) curr = (curr + 1) % numOfConsumers;

sem[curr].signal();} Не получилось –

идём дальше

Пытаемся атомарно сделать из пустого слота полный

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

N производителей, M потребителей

40

1 2 K3

? ? ? ?

? ? ? ?

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

N производителей, M потребителей

41

1 2 K3

? ? ? ?

? ? ? ?

гонка!

гонка!

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

N производителей, M потребителей

42

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

CAS(full, empty)

CAS(empty, full)

CAS(empty, full)

CAS(empty, full)

1 2 K3

CAS – атомарное сравнение с обменом

CAS – compare and swap (compare exchange)

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

N производителей, M потребителей

43

consumer (вариант):

task = null;curr = 0;while (true) {

task = slot[curr]; if (slot[curr] != null) if (!slot[curr].CAS(task, null)) curr = (curr + 1) % numOfSlots;

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

Пытаемся атомарно сделать из полного слота пустой

Не получилось – идём дальше

Запоминаем состояние слота и проверяем, не пустой ли он

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

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

44

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

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

45

T T T T

head

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

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

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

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

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

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

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

46

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 47: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

47

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 48: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

Функция push

48

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 49: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

Функция push

49

T

head

TTT

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

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

Функция push

50

T

head

TTT

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

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

T

head

TTT

T

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

Функция push

51

T

head

TTT

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

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

T

head

TTT

T

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

T

head

TTT

T

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

Функция push – гонка

52

T

head

TTT

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

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

T

head

TTT

T T

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

Функция push

53

T

head

TTT

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

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

T

head

TTT

T

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

T

head

TTT

T

T T

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

Функция push

54

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 55: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

55

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

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

Функция pop

56

T

head

TTT

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

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

T

head

TTT

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

T

head

TT

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

Функция pop

57

T

head

TTT

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

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

T

head

TTT

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

T

head

TT

B

A

A Bhead

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

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

58

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

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

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

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

59

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

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

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

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

60

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 61: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

61

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 62: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

Проблема АВА

62

4

old_head

321

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

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

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

Проблема АВА

63

4

old_head

321

head->next

43

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

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

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

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

Проблема АВА

64

4

old_head

321

head->next

43

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

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

435

old_head old_head->next

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

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

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

Проблема АВА

65

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 66: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

Проблема АВА

66

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 67: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

67

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

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

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

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

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

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

7. и т.д.

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

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

68

// количество потоков, выполняющих 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 69: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

69

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 70: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

70

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 71: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

71

// добавляем захваченный список в общий список узлов,// подлежащих удалению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 72: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

72

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 73: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

73

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 74: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

74threads_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 75: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

75

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

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

76

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 77: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

77

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 78: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

78

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 79: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

79

4

old_head

321head

hp

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

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

80

4

old_head

321head

temp = old_head

temp

hp

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

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

81

4

old_head

321head

hp

temp = old_head

temp

hp.store(old_head)

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

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

82

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 83: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

83

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 84: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

84

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 85: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

85

После того, как поток А успешно выполнил 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 86: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

86

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 87: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

87

4321head

1 5 6 7 m432

max_hazard_pointers

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

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

88

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 89: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

89

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 90: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

90

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 91: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

91

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 92: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

92

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 93: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

93

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 94: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

94

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

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

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

95

// вернуть указатель опасности для текущего потока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 96: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

96

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 97: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

97

// добавить элемент в список на удаление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 98: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

98

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 99: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

99

4321

nodes_to_reclaim

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

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

100

nodes_to_reclaim

4321current

current = nodes_to_reclaim. exchange(nullptr);

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

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

101

nodes_to_reclaim

432current

1

1

add_to_reclaim_list(current);

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

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

102

nodes_to_reclaim

432current

1

delete current;

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

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

103

nodes_to_reclaim

43current

1 5

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

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

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

104

nodes_to_reclaim

4current

1 5 3

add_to_reclaim_list(current);

1

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

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

105

nodes_to_reclaim

4current

1 5 3

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

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

106

nodes_to_reclaim

current

1 5 3

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

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

107

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

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

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

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

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

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

108

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 109: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

109

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

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

110

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

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

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

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

111

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

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

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

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

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

112

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

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

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

...

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

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

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

113

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 114: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

114

...

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 115: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

115

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

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

116

counted_node_ptr

node

internal_count

data

next

external_count

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

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

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

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

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

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

117

head

1 2 3

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

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

118

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 119: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

119

~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 120: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

120

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 121: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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; } }

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

121

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

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; } }

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

122

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

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

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; } }

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

123

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

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

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

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; } }

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

124

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

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

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

0 0

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

125

1

0

1 1

head

1 2 3

Сценарий 1:

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

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

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

0 0

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

126

2 1 1

head

1 2 3

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

0

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

0 0 0

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

127

2 1 1

head

1 2 3

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

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

0 0 0

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

128

2 1 1

head

1 2 3

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

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

0 0 0

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

129

2 1 1

head

1 2 3

Поток A:delete ptr

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

0 0 0

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

130

1 1 1

head

1 2 3

Сценарий 2:

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

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

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

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

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

0 0 0

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

131

2 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

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

0 0 0

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

132

3 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

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

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

-1 0 0

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

133

3 1 1

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 134: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

0 0 0

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

134

x 1 1

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 135: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

0 0 0

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

135

x 1 1

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 136: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

0 0 0

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

136

1 1 1

head

1 2 3

Сценарий 3:

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

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

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

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

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

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

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

0 0 0

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

137

3 1 1

head

1 2 3

Поток B: increase_head_count(old_head)

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

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

1 0 0

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

138

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 139: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

0 0 0

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

139

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 140: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

140

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 141: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

141

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

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

142

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 143: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

143

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 144: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

144

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 145: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

145

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 146: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

146

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 147: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

147

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 148: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

148

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 149: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

149

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 150: ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования блокировок

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

150

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

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

Разработка эффективного потокобезопасного неблокируемого стека

151

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

Схема устранения парных операций

152

Стек

a b c

head

T3: pop()

T1: pop()

T2: push(d)

Узкое место!■ Загрузка кэш-

памяти■ Загрузка шины

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

Схема с задержкой

153

Стек

a b c

head

T1: push(x)

trypush(x) { auto new_node = new node{x}; node_node->next = head.load(); return head.CAS(new_node->next, new_node));

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

Схема с задержкой

154

Стек

a b c

head

T1: push(x)

x

trypush(x) { auto new_node = new node{x}; node_node->next = head.load(); return head.CAS(new_node->next, new_node));

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

Схема с задержкой

155

Стек

a b c

T1: trypush(x)

trypush(x) { auto new_node = new node{x}; node_node->next = head.load(); return head.CAS(new_node->next, new_node));

CAS failed

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

Схема с задержкой

156

Стек

a b c

T1: trypush(x)

trypush(x) { auto new_node = new node{x}; node_node->next = head.load(); return head.CAS(new_node->next, new_node));

T1: backoff

e.g. exponential backoff

CAS failed

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

Схема с задержкой

157

Стек

a b c

T1: trypush(x)

trypush(x) { auto new_node = new node{x}; node_node->next = head.load(); return head.CAS(new_node->next, new_node));

T1: backoff

T1: trypush(x)

e.g. exponential backoff

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

Схема устранения парных операций

158

Речной вокзал – Цветной проезд

1235

■ Пассажиры “состязаются” за водителя маршрутки (contention), который обслуживает их последовательно

■ Водитель маршрутки – “узкое место” (bottleneck)

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

Стек

Схема устранения парных операций

159

a b c

head

Вспомогательный массив(elimination array)

T3: pop()

T1: pop()

T2: push(d)

T1: return(d)

T2: ok

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

Стек

Схема устранения парных операций

160

a b c

head

Вспомогательный массив(elimination array)

T3: pop()

T1: pop()

T2: push(d)

T1: return(d)

T2: ok

exchanger

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

Схема устранения парных операций

161

class exchanger { atomic<T*> slot{nullptr}; atomic<int> state{EMPTY};

public: auto exchanger(T* myitem, int waittime, T* result) { for (;;) {

if (timeout(waittime)) return TIME_ELAPSED_CODE;

auto curritem = slot.load(); auto currstate = state.load(); switch (currstate) {

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

Схема устранения парных операций

162

case EMPTY: if (slot.CAS(curritem, myitem) && state.CAS(EMPTY, WAITING)) {

while (time < waittime) { curritem = slot.load();

if (state == BUSY) { slot.store(nullptr); state.store(EMPTY); return curritem; } }

if (slot.CAS(myitem, nullptr) && state.CAS(WAITING, EMPTY)) { return TIME_ELAPSED_CODE; } else { curritem = slot.load(); slot.store(nullptr); state.store(EMPTY); return curritem; } }

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

Схема устранения парных операций

163

case WAITING: if slot.CAS(curritem, item) && state.CAS(WAITING, BUSY) return curritem; case BUSY: break; default: ... } } }}

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

Схема устранения парных операций

164

case EMPTY: if (slot.CAS(curritem, myitem) && state.CAS(EMPTY, WAITING)) {

while (time < waittime) { old_item = slot.load();

if (state == BUSY) { slot.store(nullptr); state.store(EMPTY); return old_item; } }

if (slot.CAS(myitem, nullptr) && state.CAS(WAITING, EMPTY)) { return TIME_ELAPSED_CODE; } else { item = slot.load(); slot.store(nullptr); state.store(EMPTY); return item; } }

Оба CAS должны выполниться атомарно – как транзакция, т.е. либо выполниться оба, либо не выполниться вообще!

Оба CAS должны выполниться атомарно – как транзакция, т.е. либо выполниться оба, либо не выполниться вообще!

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

class exchanger { struct content_t { T* slot{nullptr}; int state{EMPTY}; };

atomic<content_t> content;

public: auto exchanger(T* myitem, int waittime, T* result) { for (;;) {

if (timeout(waittime)) return TIME_ELAPSED_CODE;

auto curritem = slot; auto currstate = state; switch (currstate) {

Схема устранения парных операций

165

4 байта8 байт 12 байт > машинного

слова (8 байт)

CAS выполнится не атомарно

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

class exchanger { struct content_t { T* slot{nullptr}; int state{EMPTY}; };

atomic<content_t*> content;

public: auto exchanger(T* myitem, int waittime, T* result) { for (;;) {

if (timeout(waittime)) return TIME_ELAPSED_CODE;

auto curritem = slot.load(); auto currstate = state.load(); switch (currstate) {

Схема устранения парных операций

166

CAS атомарный!

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

Схема устранения парных операций

167

case EMPTY: auto old_content = content; if (old_content->state == EMPTY) {

auto new_content = new content{myitem, WAITING};

if (content.CAS(old_content, new_content) {

while (time < waittime) { old_item = content->slot.load();

if (state == BUSY) { content->slot.store(nullptr); content->state.store(EMPTY); return old_item; } } delete old_content; } }

// ...

// Остальные пары CAS модифицируем аналогичным образом.

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

Схема устранения парных операций

168

T1: item1

state EMPTY

slot null

curritem

currstate

slot.load();

state.load();

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

slot null

Схема устранения парных операций

169

T1: item1

state EMPTYCAS(old_content,

new_content)

curritem

currstate

null

EMPTY

old_content

content

item1

WAITING

new_content

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

Схема устранения парных операций

170

state WAITING

T1: item1

slot item1

curritem

currstate

null

EMPTY

old_content

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

Схема устранения парных операций

171

state WAITING

T1: item1

slot item1

curritem

currstate

null

EMPTY

state.load() == BUSY?

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

Схема устранения парных операций

172

state WAITING

T1: item1

slot item1

curritem

currstate

slot.load();

state.load();

T2: item2

old_content

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

Схема устранения парных операций

173

state WAITING

T1: item1

slot item1

curritem

currstate

T2: item2

old_contentnew_content

item1

WAITING

item2

BUSY

CAS(old_content,

new_content)

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

slot item2

Схема устранения парных операций

174

state BUSY

T1: item1 T2: item2

curritem

currstate

old_content

item1

WAITING

return curritem

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

Схема устранения парных операций

175

slot item2

T1: item1 T2: item2

curritem

currstate

null

BUSY

state.load() == BUSY?

state BUSY

YES!

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

Схема устранения парных операций

176

state item2

T1: item1 T2: item2

curritem

currstate

item2

BUSY

slot BUSY

state.load()

return curritem

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

Стек

Схема устранения парных операций

177

a b c

head

Вспомогательный массив(elimination array)

T3: pop()

T1: pop()

T2: push(d)

В худшем случае (если встречаются следуют не парно), вершина становится узким местом!

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

Elimination Tree (дерево устранения)

178

2

5

34

0

0

0

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

Линеаризация linearization

Согласованность относительно “состояния покоя” quiescent consistency

Линеаризация – такое выполнение параллельных операций, при котором они соответствуют какому-то последовательному выполнению операций со стеком. Т.е. потокобезопасный стек (или другая структура) “ведёт себя” как обычный последовательный стек.

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

Elimination Tree (дерево устранения)

179

2

5

34

0

0

0

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

Линеаризация linearization

Согласованность относительно “состояния покоя” quiescent consistency

Согласованность относительно “состояния покоя” – такое выполнение параллельных операций, при котором операции могут выполняться со стеком одновременно до момента, когда наступает “тишина”, т.е. нет текущих операций со структурой.

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

Elimination Tree (дерево устранения)

180

2

5

34

0

0

0

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

Добавление 5 элементов 5 разными потоками

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

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

Elimination Tree (дерево устранения)

181

2

5

34

0

0

0

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

182

2

5

34

1

0

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

183

5

34

0

0

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

184

5

3

4

0

0

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

185

5

1

41

0

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

2

3

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

186

5

4

0

1

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

3

2

1

T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

187

5

1

0

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

3

2

1

4T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

188

5

1

1

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4T1: push(1)T2: push(2)T3: push(3)T4: push(4)T5: push(5)

Есть состояние покоя!

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

Elimination Tree (дерево устранения)

189

5

1

1

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4T1: pop()T2: pop()

1

2

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

190

5

0

1

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4T1: pop()T2: pop()

1

2

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

191

5

1

1

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4T1: pop()T2: pop()

2

T1:return(5)

Нет состояния покоя!

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

Elimination Tree (дерево устранения)

192

5

1

1

1

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4T1: pop()T2: pop()

T2:return(5)Есть состояние покоя!

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

Elimination Tree (дерево устранения)

193

5

1

1

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4

Корень дерева становится узким местом!

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

Elimination Tree (дерево устранения)

194

5

1

1

0

lock-freestack

lock-freestack

lock-freestack

lock-freestack

путь 0

путь 1

1

2

3

4

Корень дерева становится узким местом!

И узлы тоже!

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

Elimination Tree с Elimination Arrays

195

5

1

1

1

2

3

4

Elimination array длины L

T1:push(6)

T2:pop()

T5:push(7)

1

Elimination array длины L / 2

T3:pop()

T4:pop()

T5:ok T4: return(7)

T3:return(5)

T2:return(6)

A:ok