SlideShare une entreprise Scribd logo
1  sur  132
Télécharger pour lire hors ligne
Лекция 7. Многопоточное
программирование без блокировок.
Модель потребитель-производитель.
Потокобезопасный стек
Пазников Алексей Александрович
Кафедра вычислительных систем СибГУТИ
Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/
Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home
Параллельные вычислительные технологии
Осень 2014 (Parallel Computing Technologies, PCT 14)
Программирование без
блокировок
2
Если вы думаете, что программирование без блокировок
это просто, значит или вы - один из тех 50, которые умеют
это делать, или же используете атомарные инструкции
недостаточно аккуратно.
Герб Саттер
Цели многопоточного программирования без блокировок
3
Однопоточная программа
Многопоточная программа
с блокировками
Многопоточная программа
без блокировок
Цели многопоточного программирования без блокировок
▪ Повышение масштабируемости путём
сокращения блокировок и ожиданий в алгоритмах
и структурах данных.
▪ Решение проблем, связанных с блокировками:
▫ Гонки: нужно не забыть заблокировать,
причём именно нужный участок кода
▫ Дедлоки: необходимо запирать в нужном
порядке различные потоки
▫ Сложность выбора критической секции
(простота или масштабируемость)
▫ Голоданеие, инверсия приоритетов и др.
4
Виды алгоритмов, свободных от блокировок
▪ Свободные от ожиданий (wait-free). “Никто никогда не ждёт”.
Каждая операция завершается за N шагов без каких-либо условий.
Гарантии:
▫ максимум пропускной способности системы
▫ отсутствие голодания
▪ Свободные от блокировок (lock-free). “Всегда какой-то из потоков
работает”. Каждый шаг приближает итоговое решение. Гарантии:
▫ максимум пропускной способности системы
▫ отсутствие голодания (один поток может постоянно ожидать)
▪ Свободные от остановок (obstruction-free). “Поток работает, если
нет конфликтов”. За ограниченное число шагов один поток, при
условии, что другие потоки остановлены, достигает результата.
▫ Все потоки не блокируются из-за проблем (задержек, ошибок) с
другими потоками.
▫ Не гарантируется прогресс, если одновременно работают два
или больше потоков. 5
Реализация спинлока на основе атомарного флага
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
Реализация модели
потребитель-производитель
без блокировок
7
Производитель-потребитель на основе блокировок
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();
}
9
Производитель-потребитель на основе блокировок
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
Производитель-потребитель без блокировок
11
is
empty?
empty
is
empty?
is
empty?
empty
is
empty?
is
empty?
empty
is
empty?
is
empty?
empty
is
empty?
Производитель-потребитель без блокировок
12
put out
is
empty?
is
empty?
empty
put out
full
is
empty?
empty full
is
empty?
is
empty?
is
empty?
Производитель-потребитель без блокировок
13
is
empty?
is
empty?
empty
put in
full
is
empty?
empty full
is
empty?
process process
is
empty?
Производитель-потребитель без блокировок
14
is
empty?
is
empty?
put in
full
is
empty?
empty full
is
empty?
process process
full
is
empty?
Производитель-потребитель без блокировок
15
is
empty?
is
empty?
full
is
empty?
full
is
empty?
is
empty?
empty full
done done
put in
done flag
put output out
Производитель-потребитель без блокировок
16
is
empty?
full
is
empty?
full
is
empty?
done done
full
done
put out
empty
is
empty?
put in
done flag
Производитель-потребитель без блокировок
17
full
is
empty?
full
is
empty?
done done
full
done
is
empty?
is
empty?
done
full
put out
Производитель-потребитель без блокировок
18
full
is
empty?
full
is
empty?
done done
full
done
is
empty?
is
empty?
done
full
Производитель-потребитель без блокировок
19
Empty Task Done
Start
End
Производитель
Потребитель
Производитель
20
curr = 0; // указатель на текущий слот
while (ThereAreMoreTasks()) {
task = AllocateAndBuildNewTask();
while (slot[curr] != null) // если null, то проверить
curr = (curr + 1) % numOfConsumers; // следующий слот
slot[curr] = task;
sem[curr].signal();
}
Производитель
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;
}
Потребитель
22
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) == null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null; // помечаем слот пустым
DoWork(task); // выполняем задачу
} // вне критической секции
}
Потребитель
23
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) == null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null; // помечаем слот пустым
DoWork(task); // выполняем задачу
} // вне критической секции
}
Как применить модель памяти С++?
Производитель, модель памяти С++
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;
}
Птребитель, модель памяти С++
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);
}
}
Производитель-потребитель, класс алгоритма
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;
}
Алгоритм - свободный от ожиданий, свободный от
блокировок или свободный от остановок?
Производитель-потребитель, класс алгоритма
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:
Свободный от ожиданий
Производитель-потребитель без блокировок
28
task = null;
while (task != done)
// Дождаться, когда слот будет полным
while ((task = slot[mySlot]) == null)
sem[mySlot].wait();
if (task != done) {
slot[mySlot] = null;
DoWork(task);
}
}
можно ли поменять две строки?
нужно ли это сделать?
Реализация стека,
свободного от блокировок,
на основе сборщика мусора
29
Стек, свободный от блокировок
30
T T T T
head
1. Конструктор
2. Деструктор
3. Поиск узла (find)
4. Добавление узла (push)
5. Удаление узла (pop)
Стек, свободный от блокировок
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}; // атомарный указатель
}; // на голову списка
Конструктор и деструктор
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;
}
}
Функция 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} {}
};
Функция push
34
T
head
TTT
Начальная стадия
Промежуточная стадия
T
head
TTT
T
Конечная стадия
T
head
TTT
T
Функция push
35
T
head
TTT
Начальная стадия
Промежуточная стадия выполняется двумя потоками
T
head
TTT
T
Первый добавляемый элемент пропал, остался только второй
T
head
TTT
T
T T
Функция 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.
Функция pop (ошибочная)
37
void pop(T& result) {
node* old_head = head.load();
head = head->next;
result = old_head->data;
}
Функция pop
38
T
head
TTT
Начальная стадия
Промежуточная стадия
T
head
TTT
Конечная стадия
T
head
TT
Функция pop
39
T
head
TTT
Начальная стадия
Промежуточная стадия
T
head
TTT
Конечная стадия
T
head
TT
B
A
A B
Функция 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;
}
Функция 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;
}
Функция 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>();
}
Функция 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>();
}
Проблема АВА
44
4
old_head
321
head->next
Поток А выполняет удаление узла
из вершины стека
Проблема АВА
45
4
old_head
321
head->next
43
Поток А выполняет удаление узла
из вершины стека
old_head old_head->next
Поток А был вытеснен и другие
потоки удалили два узла из стека
Проблема АВА
46
4
old_head
321
head->next
43
Поток А был вытеснен и другие
потоки удалили два узла из стека
Поток А выполняет удаление узла
из вершины стека
435
old_head old_head->next
old_head old_head->next
Некий поток добавил новый узел и
аллокатор выделил под него ту же память
Проблема АВА
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
Поток А был вытеснен и другие
потоки удалили два узла из стека
Проблема АВА
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
Поток А был вытеснен и другие
потоки удалили два узла из стека
Решения проблемы АВА
49
1. “Ленивый” сборщик мусора
2. Указатели опасности
3. Счётчик ссылок на элемент
4. Сделать узлы уникальными
5. Вообще не удалять узлы
6. Добавление дополнительных узлов
7. и т.д.
Функция pop (наивная)
50
// количество потоков, выполняющих pop
std::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;
}
Функция 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;
}
}
Функция 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;
}
}
Функция 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);
}
Функция try_reclaim освобождения удалённых узлов
54
4
head
321
delete_list 0threads_in_pop
4
head
321
delete_list
threads_in_pop 1
5
5
A A удаляет узел 1 и
вытесняется в pop() после
1-го чтения threads_in_pop
Функция try_reclaim освобождения удалённых узлов
55
4
head
32
delete_list 2threads_in_pop
43
delete_list
threads_in_pop 2
5
2
С удаляет узел и
продолжает работать до
момента выхода из pop()
old_head
B
B вызывает pop() и
вытесняется после 1-го
чтения head
A
head
old_head
B
4
C
5
A
Функция try_reclaim освобождения удалённых узлов
56threads_in_pop 2
43
delete_list
threads_in_pop 2
2
A возобновляет выполнение
и захватывает список на
удаление. После этого он
должен 2-й раз проверить,
один ли он в pop()
head
old_head
B
2
5
A
43
head
old_head
B
2
delete_list A
2 5
delete_list
B возобновляет выполнение,
выполняет CAS и переходит
к 3 узлу
Реализация стека,
свободного от блокировок,
на основе указателей
опасности
57
Указатели опасности (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
Функция 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);
// ...
}
Функция 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);
Указатели опасности (hazard pointers)
61
4
old_head
321
head
hp
Указатели опасности (hazard pointers)
62
4
old_head
321
head
temp = old_head
temp
hp
Указатели опасности (hazard pointers)
63
4
old_head
321
head
hp
temp = old_head
temp
hp.store(old_head)
Указатели опасности (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
Указатели опасности (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, с котором
мы будем работать (сдвигать указатель на следующий элемент)
Проверка позволяет определить, не изменился ли головной элемент с
тех пор, когда мы запомнили его в указателе опасности.
Указатели опасности (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.
Указатели опасности (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
Функция 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;
}
Реализация указателей опасности
69
4321
head
1 5 6 7 m432
max_hazard_pointers
Реализация указателей опасности
70
4321
head
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
Реализация указателей опасности
71
4321
head
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
Реализация указателей опасности
72
4321
head
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
Реализация указателей опасности
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;
Реализация указателей опасности
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");
}
Реализация указателей опасности
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());
}
Реализация указателей опасности
76
// вернуть указатель опасности для текущего потока
std::atomic<void*>&
get_hazard_pointer_for_current_thread() {
thread_local static hp_owner hazard;
return hazard.get_pointer();
}
Реализация указателей опасности
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;
}
Реализация функции освобождения памяти
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;
Реализация функции освобождения памяти
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));
}
Реализация функции освобождения памяти
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;
}
}
Реализация функции освобождения памяти
81
4321
nodes_to_reclaim
Реализация функции освобождения памяти
82
nodes_to_reclaim
4321
current
current = nodes_to_reclaim.
exchange(nullptr);
Реализация функции освобождения памяти
83
nodes_to_reclaim
432
current
1
1
add_to_reclaim_list(current);
Реализация функции освобождения памяти
84
nodes_to_reclaim
432
current
1
delete current;
Реализация функции освобождения памяти
85
nodes_to_reclaim
43
current
1 5
add_to_reclaim_list()
при выполнении pop()
Реализация функции освобождения памяти
86
nodes_to_reclaim
4
current
1 5 3
add_to_reclaim_list(current);
1
Реализация функции освобождения памяти
87
nodes_to_reclaim
4
current
1 5 3
Реализация функции освобождения памяти
88
nodes_to_reclaim
current
1 5 3
Недостатки указетелей опасности
89
1. Просмотр массива указателей опаности требует в худшем случае
max_hazard_pointers атомарных переменных.
2. Атомарные операции могут работать медленнее эквивалентных
обычных операций
3. При освобождении узла также требуется просмотреть список
указателей опаности, т.е. max_hazard_pointers в худшем случае.
Функция pop дорогостоящая. Решения?
Недостатки указетелей опасности
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
узлов.
Реализация стека,
свободного от блокировок,
с помощью умного
указателя
91
Реализация на основе атомарного умного указателя
92
▪ Удалять узлы можно только при отсутствии
обращения к ним из других потоков
▪ Если на узел нет ссылки, то его можно
удаляь
Реализация на основе атомарного умного указателя
93
▪ Удалять узлы можно только при отсутствии
обращения к ним из других потоков
▪ Если на узел нет ссылки, то его можно
удаляь
▪ Умный указатель shared_ptr как раз
решает эту задачу!
Реализация на основе атомарного умного указателя
94
▪ Удалять узлы можно только при отсутствии
обращения к ним из других потоков
▪ Если на узел нет ссылки, то его можно
удаляь
▪ Умный указатель shared_ptr как раз
решает эту задачу!
...
▪ Но, к сожалению, атомарные операции
shared_ptr в большинстве реализаций не
свободны от блокировок.
Реализация на основе атомарного умного указателя
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;
Реализация на основе атомарного умного указателя
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>();
}
Реализация стека,
свободного от блокировок,
с помощью подсчёта ссылок
97
Двойной счётчик ссылок
98
counted_node_ptr
node
internal_count
data
next
external_count
▪ При начале каждого чтения внешний счётчик увеличивается.
▪ При завершении чтения внутренний счётчик уменьшается.
▪ При удалении узла внутренний счетчик увеличивается на
величину внешнего минус 1, а внешний отбрасывается.
▪ Если внутренний счётчик равен 0, узел можно удалять.
Двойной счётчик ссылок
99
head
1 2 3
Двойной счётчик ссылок
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;
Двойной счётчик ссылок
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));
}
};
Двойной счётчик ссылок
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;
}
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
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. Проверить указатель на пустоту
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)
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. Если другие потоки на узел не ссылаются, освободить память
(убрать за тем потоком, который выполнил удаление)
0 0
Двойной счётчик ссылок
107
1
0
1 1
head
1 2 3
Сценарий 1:
Поток А эксклюзивно удаляет узел.
Другие потоки ему не мешают.
0 0
Двойной счётчик ссылок
108
2 1 1
head
1 2 3
Поток A:
increase_head_count(old_head)
node* const = old_head.ptr
0
0 0 0
Двойной счётчик ссылок
109
2 1 1
head
1 2 3
Поток A:
head.compare_exchange(old_head,
ptr->next)
0 0 0
Двойной счётчик ссылок
110
2 0 0
head
1 2 3
Поток A:
count_increase = 2 - 2 = 0
internal_count = 0 + 0 = 0
0 0 0
Двойной счётчик ссылок
111
2 0 0
head
1 2 3
Поток A:
delete ptr
0 0 0
Двойной счётчик ссылок
112
1 1 1
head
1 2 3
Сценарий 2:
Потоки А и В одновременно удаляют узел.
Потоку А удаётся выполнить удаление узла вперёд B.
Поток В успевает выйти из pop до того,
как А попробует освободить узел.
0 0 0
Двойной счётчик ссылок
113
2 1 1
head
1 2 3
Поток B: increase_head_count(old_head)
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(...)
-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(...)
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(...)
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
0 0 0
Двойной счётчик ссылок
118
1 1 1
head
1 2 3
Сценарий 3:
Потоки А и В одновременно удаляют узел.
Потоку А удаётся выполнить удаление узла вперёд B.
Поток В не успевает выйти из pop, когда А пытается
освободить узел, и поэтому А узел не освобождает.
Зато поток В, последним покидая узел,
освобождает память из-под узла, удалённого А.
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(...)
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 узел
не освобождает
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
Двойной счётчик ссылок - проблема
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;
Структура может не поддерживать выполнение
атомарных операций без блокировок!
Применение модели
памяти С++ для стека,
свободного от блокировок
123
Применение модели памяти С++
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;
}
Применение модели памяти С++
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
(“флага”)
Работа с добавленным элементом
Применение модели памяти С++
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
(“флага”)
Применение модели памяти С++
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;
}
}
Чтение указателя
Применение модели памяти С++
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
Применение модели памяти С++
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;
}
}
Извлечение данных
Удаление должно выполняться
после извлечения данных
Применение модели памяти С++
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;
}
}
Удаление должно выполняться
после извлечения данных
Извлечение данных
Применение модели памяти С++
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;
}
Удаление должно выполняться
после извлечения данных
Извлечение данных
Применение модели памяти С++
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

Contenu connexe

Tendances

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

Tendances (20)

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

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

статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кодаAndrey Karpov
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода Pavel Tsukanov
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Googleyaevents
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05Computer Science Club
 
Использование хранимых процедур в MySQL (Константин Осипов)
Использование хранимых процедур в MySQL (Константин Осипов)Использование хранимых процедур в MySQL (Константин Осипов)
Использование хранимых процедур в MySQL (Константин Осипов)Ontico
 
Асинхронность и сопрограммы
Асинхронность и сопрограммыАсинхронность и сопрограммы
Асинхронность и сопрограммыPlatonov Sergey
 
Сложности микробенчмаркинга
Сложности микробенчмаркингаСложности микробенчмаркинга
Сложности микробенчмаркингаAndrey Akinshin
 
Cpp0x Introduction
Cpp0x IntroductionCpp0x Introduction
Cpp0x IntroductionFedor Vompe
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаAndrey Karpov
 
Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?Andrey Karpov
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийAndrey Akinshin
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийMikhail Shcherbakov
 
Soft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровняSoft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровняLuxoftTraining
 
C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.Igor Shkulipa
 
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...Yandex
 
DSLs in Lisp and Clojure
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and ClojureVasil Remeniuk
 
Jbreak 2016: Твой личный Spring Boot Starter
Jbreak 2016: Твой личный Spring Boot StarterJbreak 2016: Твой личный Spring Boot Starter
Jbreak 2016: Твой личный Spring Boot StarterAleksandr Tarasov
 
Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Andrey Karpov
 
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2Dima Dzuba
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
 

Similaire à ПВТ - осень 2014 - Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек (20)

статический анализ кода
статический анализ кодастатический анализ кода
статический анализ кода
 
Статический анализ кода
Статический анализ кода Статический анализ кода
Статический анализ кода
 
Юнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, GoogleЮнит-тестирование и Google Mock. Влад Лосев, Google
Юнит-тестирование и Google Mock. Влад Лосев, Google
 
2012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture052012 03 14_parallel_programming_lecture05
2012 03 14_parallel_programming_lecture05
 
Использование хранимых процедур в MySQL (Константин Осипов)
Использование хранимых процедур в MySQL (Константин Осипов)Использование хранимых процедур в MySQL (Константин Осипов)
Использование хранимых процедур в MySQL (Константин Осипов)
 
Асинхронность и сопрограммы
Асинхронность и сопрограммыАсинхронность и сопрограммы
Асинхронность и сопрограммы
 
Сложности микробенчмаркинга
Сложности микробенчмаркингаСложности микробенчмаркинга
Сложности микробенчмаркинга
 
Cpp0x Introduction
Cpp0x IntroductionCpp0x Introduction
Cpp0x Introduction
 
Опыт разработки статического анализатора кода
Опыт разработки статического анализатора кодаОпыт разработки статического анализатора кода
Опыт разработки статического анализатора кода
 
Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?Статический анализ кода: Что? Как? Зачем?
Статический анализ кода: Что? Как? Зачем?
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложений
 
Распространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложенийРаспространённые ошибки оценки производительности .NET-приложений
Распространённые ошибки оценки производительности .NET-приложений
 
Soft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровняSoft labs. достижима ли в c++ эффективность языка среднего уровня
Soft labs. достижима ли в c++ эффективность языка среднего уровня
 
C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.C++ STL & Qt. Занятие 10.
C++ STL & Qt. Занятие 10.
 
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
Филипп Торчинский «Анализ производительности и отладка приложений с помощью D...
 
DSLs in Lisp and Clojure
DSLs in Lisp and ClojureDSLs in Lisp and Clojure
DSLs in Lisp and Clojure
 
Jbreak 2016: Твой личный Spring Boot Starter
Jbreak 2016: Твой личный Spring Boot StarterJbreak 2016: Твой личный Spring Boot Starter
Jbreak 2016: Твой личный Spring Boot Starter
 
Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?Статический анализ: ищем ошибки... и уязвимости?
Статический анализ: ищем ошибки... и уязвимости?
 
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
Объектно-Ориентированное Программирование на C++, Лекции 1 и 2
 
Статический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий ЛевановСтатический и динамический полиморфизм в C++, Дмитрий Леванов
Статический и динамический полиморфизм в C++, Дмитрий Леванов
 

Plus de Alexey Paznikov

Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Alexey Paznikov
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Alexey Paznikov
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIAlexey Paznikov
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Alexey Paznikov
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Alexey Paznikov
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыAlexey Paznikov
 
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...Alexey Paznikov
 
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsAlexey Paznikov
 
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюAlexey Paznikov
 
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияAlexey Paznikov
 
ПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаAlexey Paznikov
 
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 9
 ТФРВС - весна 2014 - лекция 9 ТФРВС - весна 2014 - лекция 9
ТФРВС - весна 2014 - лекция 9Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7Alexey Paznikov
 
ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6Alexey Paznikov
 

Plus de Alexey Paznikov (18)

Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
 
Лекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPIЛекция 4. Производные типы данных в стандарте MPI
Лекция 4. Производные типы данных в стандарте MPI
 
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
Лекция 3. Виртуальные топологии в MPI. Параллельные алгоритмы в стандарте MPI...
 
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
Лекция 2. Коллективные операции в MPI. Параллельные алгоритмы случайного блуж...
 
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обменыЛекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
Лекция 1. Основные понятия стандарта MPI. Дифференцированные обмены
 
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
ПВТ - весна 2015 - Лекция 1. Актуальность параллельных вычислений. Анализ пар...
 
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX ThreadsПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
ПВТ - осень 2014 - Лекция 3 - Стандарт POSIX Threads
 
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятьюПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
ПВТ - осень 2014 - Лекция 2 - Архитектура вычислительных систем с общей памятью
 
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисленияПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
ПВТ - осень 2014 - лекция 1 - Введение в параллельные вычисления
 
ПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курсаПВТ - осень 2014 - лекция 1а - Описание курса
ПВТ - осень 2014 - лекция 1а - Описание курса
 
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
Анализ эффективности выполнения алгоритма параллельной редукции в языке Cray ...
 
ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11ТФРВС - весна 2014 - лекция 11
ТФРВС - весна 2014 - лекция 11
 
ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10ТФРВС - весна 2014 - лекция 10
ТФРВС - весна 2014 - лекция 10
 
ТФРВС - весна 2014 - лекция 9
 ТФРВС - весна 2014 - лекция 9 ТФРВС - весна 2014 - лекция 9
ТФРВС - весна 2014 - лекция 9
 
ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8ТФРВС - весна 2014 - лекция 8
ТФРВС - весна 2014 - лекция 8
 
ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7ТФРВС - весна 2014 - лекция 7
ТФРВС - весна 2014 - лекция 7
 
ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6ТФРВС - весна 2014 - лекция 6
ТФРВС - весна 2014 - лекция 6
 

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

  • 1. Лекция 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home Параллельные вычислительные технологии Осень 2014 (Parallel Computing Technologies, PCT 14)
  • 2. Программирование без блокировок 2 Если вы думаете, что программирование без блокировок это просто, значит или вы - один из тех 50, которые умеют это делать, или же используете атомарные инструкции недостаточно аккуратно. Герб Саттер
  • 3. Цели многопоточного программирования без блокировок 3 Однопоточная программа Многопоточная программа с блокировками Многопоточная программа без блокировок
  • 4. Цели многопоточного программирования без блокировок ▪ Повышение масштабируемости путём сокращения блокировок и ожиданий в алгоритмах и структурах данных. ▪ Решение проблем, связанных с блокировками: ▫ Гонки: нужно не забыть заблокировать, причём именно нужный участок кода ▫ Дедлоки: необходимо запирать в нужном порядке различные потоки ▫ Сложность выбора критической секции (простота или масштабируемость) ▫ Голоданеие, инверсия приоритетов и др. 4
  • 5. Виды алгоритмов, свободных от блокировок ▪ Свободные от ожиданий (wait-free). “Никто никогда не ждёт”. Каждая операция завершается за N шагов без каких-либо условий. Гарантии: ▫ максимум пропускной способности системы ▫ отсутствие голодания ▪ Свободные от блокировок (lock-free). “Всегда какой-то из потоков работает”. Каждый шаг приближает итоговое решение. Гарантии: ▫ максимум пропускной способности системы ▫ отсутствие голодания (один поток может постоянно ожидать) ▪ Свободные от остановок (obstruction-free). “Поток работает, если нет конфликтов”. За ограниченное число шагов один поток, при условии, что другие потоки остановлены, достигает результата. ▫ Все потоки не блокируются из-за проблем (задержек, ошибок) с другими потоками. ▫ Не гарантируется прогресс, если одновременно работают два или больше потоков. 5
  • 6. Реализация спинлока на основе атомарного флага 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
  • 8. Производитель-потребитель на основе блокировок 8 Производитель Потребитель Потребитель Потребитель
  • 9. Производитель-потребитель на основе блокировок 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
  • 10. Производитель-потребитель на основе блокировок 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
  • 12. Производитель-потребитель без блокировок 12 put out is empty? is empty? empty put out full is empty? empty full is empty? is empty? is empty?
  • 14. Производитель-потребитель без блокировок 14 is empty? is empty? put in full is empty? empty full is empty? process process full is empty?
  • 19. Производитель-потребитель без блокировок 19 Empty Task Done Start End Производитель Потребитель
  • 20. Производитель 20 curr = 0; // указатель на текущий слот while (ThereAreMoreTasks()) { task = AllocateAndBuildNewTask(); while (slot[curr] != null) // если null, то проверить curr = (curr + 1) % numOfConsumers; // следующий слот slot[curr] = task; sem[curr].signal(); }
  • 21. Производитель 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; }
  • 22. Потребитель 22 task = null; while (task != done) // Дождаться, когда слот будет полным while ((task = slot[mySlot]) == null) sem[mySlot].wait(); if (task != done) { slot[mySlot] = null; // помечаем слот пустым DoWork(task); // выполняем задачу } // вне критической секции }
  • 23. Потребитель 23 task = null; while (task != done) // Дождаться, когда слот будет полным while ((task = slot[mySlot]) == null) sem[mySlot].wait(); if (task != done) { slot[mySlot] = null; // помечаем слот пустым DoWork(task); // выполняем задачу } // вне критической секции } Как применить модель памяти С++?
  • 24. Производитель, модель памяти С++ 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; }
  • 25. Птребитель, модель памяти С++ 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); } }
  • 26. Производитель-потребитель, класс алгоритма 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; } Алгоритм - свободный от ожиданий, свободный от блокировок или свободный от остановок?
  • 27. Производитель-потребитель, класс алгоритма 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: Свободный от ожиданий
  • 28. Производитель-потребитель без блокировок 28 task = null; while (task != done) // Дождаться, когда слот будет полным while ((task = slot[mySlot]) == null) sem[mySlot].wait(); if (task != done) { slot[mySlot] = null; DoWork(task); } } можно ли поменять две строки? нужно ли это сделать?
  • 29. Реализация стека, свободного от блокировок, на основе сборщика мусора 29
  • 30. Стек, свободный от блокировок 30 T T T T head 1. Конструктор 2. Деструктор 3. Поиск узла (find) 4. Добавление узла (push) 5. Удаление узла (pop)
  • 31. Стек, свободный от блокировок 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}; // атомарный указатель }; // на голову списка
  • 32. Конструктор и деструктор 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; } }
  • 33. Функция 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} {} };
  • 34. Функция push 34 T head TTT Начальная стадия Промежуточная стадия T head TTT T Конечная стадия T head TTT T
  • 35. Функция push 35 T head TTT Начальная стадия Промежуточная стадия выполняется двумя потоками T head TTT T Первый добавляемый элемент пропал, остался только второй T head TTT T T T
  • 36. Функция 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.
  • 37. Функция pop (ошибочная) 37 void pop(T& result) { node* old_head = head.load(); head = head->next; result = old_head->data; }
  • 38. Функция pop 38 T head TTT Начальная стадия Промежуточная стадия T head TTT Конечная стадия T head TT
  • 39. Функция pop 39 T head TTT Начальная стадия Промежуточная стадия T head TTT Конечная стадия T head TT B A A B
  • 40. Функция 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; }
  • 41. Функция 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; }
  • 42. Функция 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>(); }
  • 43. Функция 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>(); }
  • 44. Проблема АВА 44 4 old_head 321 head->next Поток А выполняет удаление узла из вершины стека
  • 45. Проблема АВА 45 4 old_head 321 head->next 43 Поток А выполняет удаление узла из вершины стека old_head old_head->next Поток А был вытеснен и другие потоки удалили два узла из стека
  • 46. Проблема АВА 46 4 old_head 321 head->next 43 Поток А был вытеснен и другие потоки удалили два узла из стека Поток А выполняет удаление узла из вершины стека 435 old_head old_head->next old_head old_head->next Некий поток добавил новый узел и аллокатор выделил под него ту же память
  • 47. Проблема АВА 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 Поток А был вытеснен и другие потоки удалили два узла из стека
  • 48. Проблема АВА 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 Поток А был вытеснен и другие потоки удалили два узла из стека
  • 49. Решения проблемы АВА 49 1. “Ленивый” сборщик мусора 2. Указатели опасности 3. Счётчик ссылок на элемент 4. Сделать узлы уникальными 5. Вообще не удалять узлы 6. Добавление дополнительных узлов 7. и т.д.
  • 50. Функция pop (наивная) 50 // количество потоков, выполняющих pop std::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; }
  • 51. Функция 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; } }
  • 52. Функция 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; } }
  • 53. Функция 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); }
  • 54. Функция try_reclaim освобождения удалённых узлов 54 4 head 321 delete_list 0threads_in_pop 4 head 321 delete_list threads_in_pop 1 5 5 A A удаляет узел 1 и вытесняется в pop() после 1-го чтения threads_in_pop
  • 55. Функция try_reclaim освобождения удалённых узлов 55 4 head 32 delete_list 2threads_in_pop 43 delete_list threads_in_pop 2 5 2 С удаляет узел и продолжает работать до момента выхода из pop() old_head B B вызывает pop() и вытесняется после 1-го чтения head A head old_head B 4 C 5 A
  • 56. Функция try_reclaim освобождения удалённых узлов 56threads_in_pop 2 43 delete_list threads_in_pop 2 2 A возобновляет выполнение и захватывает список на удаление. После этого он должен 2-й раз проверить, один ли он в pop() head old_head B 2 5 A 43 head old_head B 2 delete_list A 2 5 delete_list B возобновляет выполнение, выполняет CAS и переходит к 3 узлу
  • 57. Реализация стека, свободного от блокировок, на основе указателей опасности 57
  • 58. Указатели опасности (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
  • 59. Функция 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); // ... }
  • 60. Функция 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);
  • 61. Указатели опасности (hazard pointers) 61 4 old_head 321 head hp
  • 62. Указатели опасности (hazard pointers) 62 4 old_head 321 head temp = old_head temp hp
  • 63. Указатели опасности (hazard pointers) 63 4 old_head 321 head hp temp = old_head temp hp.store(old_head)
  • 64. Указатели опасности (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
  • 65. Указатели опасности (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, с котором мы будем работать (сдвигать указатель на следующий элемент) Проверка позволяет определить, не изменился ли головной элемент с тех пор, когда мы запомнили его в указателе опасности.
  • 66. Указатели опасности (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.
  • 67. Указатели опасности (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
  • 68. Функция 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; }
  • 70. Реализация указателей опасности 70 4321 head 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
  • 71. Реализация указателей опасности 71 4321 head 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
  • 72. Реализация указателей опасности 72 4321 head 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
  • 73. Реализация указателей опасности 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;
  • 74. Реализация указателей опасности 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"); }
  • 75. Реализация указателей опасности 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()); }
  • 76. Реализация указателей опасности 76 // вернуть указатель опасности для текущего потока std::atomic<void*>& get_hazard_pointer_for_current_thread() { thread_local static hp_owner hazard; return hazard.get_pointer(); }
  • 77. Реализация указателей опасности 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; }
  • 78. Реализация функции освобождения памяти 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;
  • 79. Реализация функции освобождения памяти 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)); }
  • 80. Реализация функции освобождения памяти 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; } }
  • 81. Реализация функции освобождения памяти 81 4321 nodes_to_reclaim
  • 82. Реализация функции освобождения памяти 82 nodes_to_reclaim 4321 current current = nodes_to_reclaim. exchange(nullptr);
  • 83. Реализация функции освобождения памяти 83 nodes_to_reclaim 432 current 1 1 add_to_reclaim_list(current);
  • 84. Реализация функции освобождения памяти 84 nodes_to_reclaim 432 current 1 delete current;
  • 85. Реализация функции освобождения памяти 85 nodes_to_reclaim 43 current 1 5 add_to_reclaim_list() при выполнении pop()
  • 86. Реализация функции освобождения памяти 86 nodes_to_reclaim 4 current 1 5 3 add_to_reclaim_list(current); 1
  • 87. Реализация функции освобождения памяти 87 nodes_to_reclaim 4 current 1 5 3
  • 88. Реализация функции освобождения памяти 88 nodes_to_reclaim current 1 5 3
  • 89. Недостатки указетелей опасности 89 1. Просмотр массива указателей опаности требует в худшем случае max_hazard_pointers атомарных переменных. 2. Атомарные операции могут работать медленнее эквивалентных обычных операций 3. При освобождении узла также требуется просмотреть список указателей опаности, т.е. max_hazard_pointers в худшем случае. Функция pop дорогостоящая. Решения?
  • 90. Недостатки указетелей опасности 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 узлов.
  • 91. Реализация стека, свободного от блокировок, с помощью умного указателя 91
  • 92. Реализация на основе атомарного умного указателя 92 ▪ Удалять узлы можно только при отсутствии обращения к ним из других потоков ▪ Если на узел нет ссылки, то его можно удаляь
  • 93. Реализация на основе атомарного умного указателя 93 ▪ Удалять узлы можно только при отсутствии обращения к ним из других потоков ▪ Если на узел нет ссылки, то его можно удаляь ▪ Умный указатель shared_ptr как раз решает эту задачу!
  • 94. Реализация на основе атомарного умного указателя 94 ▪ Удалять узлы можно только при отсутствии обращения к ним из других потоков ▪ Если на узел нет ссылки, то его можно удаляь ▪ Умный указатель shared_ptr как раз решает эту задачу! ... ▪ Но, к сожалению, атомарные операции shared_ptr в большинстве реализаций не свободны от блокировок.
  • 95. Реализация на основе атомарного умного указателя 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;
  • 96. Реализация на основе атомарного умного указателя 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>(); }
  • 97. Реализация стека, свободного от блокировок, с помощью подсчёта ссылок 97
  • 98. Двойной счётчик ссылок 98 counted_node_ptr node internal_count data next external_count ▪ При начале каждого чтения внешний счётчик увеличивается. ▪ При завершении чтения внутренний счётчик уменьшается. ▪ При удалении узла внутренний счетчик увеличивается на величину внешнего минус 1, а внешний отбрасывается. ▪ Если внутренний счётчик равен 0, узел можно удалять.
  • 100. Двойной счётчик ссылок 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;
  • 101. Двойной счётчик ссылок 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)); } };
  • 102. Двойной счётчик ссылок 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; }
  • 103. 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
  • 104. 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. Проверить указатель на пустоту
  • 105. 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)
  • 106. 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. Если другие потоки на узел не ссылаются, освободить память (убрать за тем потоком, который выполнил удаление)
  • 107. 0 0 Двойной счётчик ссылок 107 1 0 1 1 head 1 2 3 Сценарий 1: Поток А эксклюзивно удаляет узел. Другие потоки ему не мешают.
  • 108. 0 0 Двойной счётчик ссылок 108 2 1 1 head 1 2 3 Поток A: increase_head_count(old_head) node* const = old_head.ptr 0
  • 109. 0 0 0 Двойной счётчик ссылок 109 2 1 1 head 1 2 3 Поток A: head.compare_exchange(old_head, ptr->next)
  • 110. 0 0 0 Двойной счётчик ссылок 110 2 0 0 head 1 2 3 Поток A: count_increase = 2 - 2 = 0 internal_count = 0 + 0 = 0
  • 111. 0 0 0 Двойной счётчик ссылок 111 2 0 0 head 1 2 3 Поток A: delete ptr
  • 112. 0 0 0 Двойной счётчик ссылок 112 1 1 1 head 1 2 3 Сценарий 2: Потоки А и В одновременно удаляют узел. Потоку А удаётся выполнить удаление узла вперёд B. Поток В успевает выйти из pop до того, как А попробует освободить узел.
  • 113. 0 0 0 Двойной счётчик ссылок 113 2 1 1 head 1 2 3 Поток B: increase_head_count(old_head)
  • 114. 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(...)
  • 115. -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(...)
  • 116. 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(...)
  • 117. 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
  • 118. 0 0 0 Двойной счётчик ссылок 118 1 1 1 head 1 2 3 Сценарий 3: Потоки А и В одновременно удаляют узел. Потоку А удаётся выполнить удаление узла вперёд B. Поток В не успевает выйти из pop, когда А пытается освободить узел, и поэтому А узел не освобождает. Зато поток В, последним покидая узел, освобождает память из-под узла, удалённого А.
  • 119. 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(...)
  • 120. 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 узел не освобождает
  • 121. 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
  • 122. Двойной счётчик ссылок - проблема 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; Структура может не поддерживать выполнение атомарных операций без блокировок!
  • 123. Применение модели памяти С++ для стека, свободного от блокировок 123
  • 124. Применение модели памяти С++ 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; }
  • 125. Применение модели памяти С++ 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 (“флага”) Работа с добавленным элементом
  • 126. Применение модели памяти С++ 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 (“флага”)
  • 127. Применение модели памяти С++ 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; } } Чтение указателя
  • 128. Применение модели памяти С++ 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
  • 129. Применение модели памяти С++ 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; } } Извлечение данных Удаление должно выполняться после извлечения данных
  • 130. Применение модели памяти С++ 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; } } Удаление должно выполняться после извлечения данных Извлечение данных
  • 131. Применение модели памяти С++ 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; } Удаление должно выполняться после извлечения данных Извлечение данных
  • 132. Применение модели памяти С++ 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