Максим Хижинский, CoreHard.by Минск, 2016
Lock-free maps изнутриLock-free maps изнутри
QueueQueue
Максим Хижинский, CoreHard.by Минск, 2016
MapMap
Максим Хижинский, CoreHard.by Минск, 2016
Lock-free ordered listLock-free ordered list
Операции:
●
insert( node )
● erase( key )
●
find( key )
template <class T>
struct node {
std::atomic<node*> next_;
T data_;
};
H T52 8
Lock-free примитивы:
● atomic load/store
●
atomic compare-and-swap (CAS)
Максим Хижинский, CoreHard.by Минск, 2016
CAS — compare-and-swapCAS — compare-and-swap
template <typename T>
bool CAS( T * pAtomic, T expected, T desired )
atomically {
if ( *pAtomic == expected ) {
*pAtomic = desired;
return true;
}
else
return false;
};
bool std::atomic<T>::compare_exchange(
T& expected, T desired );
Максим Хижинский, CoreHard.by Минск, 2016
Lock-free list: insertLock-free list: insert
H T52 8
3
H T52 8
3
3. prev->next_.CAS( next, new_node )
1. find insert position for key 3
2. new_node.next_.store( next )
H T52 8
prev next
new_node
Максим Хижинский, CoreHard.by Минск, 2016
Local varsLocal vars
H T52 8
prev
3
found
erase( Key k ) {
retry:
node * prev = Head;
do {
node * found = prev->next_.load();
if ( found->key == k ) {
node * next = found->next_.load();
if (prev->next_.CAS( found, next )) {
delete found;
return true;
}
else
goto retry;
}
prev = found;
} while ( found->key < k );
return false;
}
Проблема: локальные ссылки
next
Максим Хижинский, CoreHard.by Минск, 2016
ABA-проблемаABA-проблема
52
prev
3
found next
Thread A: erase(3) Thread B
52 3
erase(3); erase(5)
2 3
insert(4)
Heap
new node(4) alloc
delete
42
preempted...
42
prev found next
5
addr(3) == addr(4)
prev->next_.CAS( found, next ) - success!!!
2 мусор
5
Максим Хижинский, CoreHard.by Минск, 2016
Tagged pointersTagged pointers
pointer tag
prev->next_.dwCAS( found, {next.ptr, prev->next_.tag + 1} )
template <class T>
struct tagged_ptr {
T * ptr;
uintptr_t tag;
};
Требует dwCAS — не везде есть
Решает только ABA-проблему
Освободить память нельзя,
нужен free-list
[ boost.lock-free ]
H T52 8
prev
3
found next
Максим Хижинский, CoreHard.by Минск, 2016
Hazard pointersHazard pointers
H T52 8
prev
3
found
erase( Key k ) {
hp_guard hp1 = get_guard();
hp_guard hp2 = get_guard();
retry:
node * prev = Head;
do {
node * found = hp2.protect( prev->next_);
if ( found->key == k )
if (prev->next_.CAS( found, found->next_.load())) {
hp_retire( found );
return true;
}
else
goto retry;
}
hp1 = hp2;
prev = found;
} while ( found->key < k );
return false;
}
Распределяем HP (TLS)
Защищаем элемент
Отложенное удаление
Максим Хижинский, CoreHard.by Минск, 2016
Hazard PointersHazard Pointers
Объявление Hazard Pointer'а – защита локальной ссылки
class hp_guard {
void * hp;
// ...
};
T * hp_guard::protect(std::atomic<T*>& what) {
T * t;
do {
hp = t = what.load();
} while (t != what.load());
return t;
}
0
1
…
K - 1
thread-local
●
HP[K]
Другой поток может
изменить what, поэтому - цикл
Максим Хижинский, CoreHard.by Минск, 2016
Hazard PointersHazard Pointers
Удаление элемента
void hp_retire( T * what ) {
push what to current_thread.Retired array
if ( current_thread.Retired is full )
hp.Scan( current_thread );
}
// сердце hazard pointer
void hp::Scan( current_thread ) {
void * guarded[K*P] = union HP[K] for all P thread;
foreach ( p in current_thread.Retired[R] )
if ( p not in guarded[] )
delete p;
}
0
1
2
…
R - 1
Retired[R]
0
1
…
K - 1
HP[K]
thread-local
Максим Хижинский, CoreHard.by Минск, 2016
Hazard pointersHazard pointers
✔ Использует только атомарные чтение/запись
 Защищает только локальные ссылки
✔ Размер массива отложенных (готовых к
удалению) элементов ограничен сверху
 Перед работой с указателем его следует
объявить как hazard
решает ABA-проблему
Физическое удаление элементов
Максим Хижинский, CoreHard.by Минск, 2016
Epoch-based SMREpoch-based SMR
Map.find( ... );
Set.insert( ... );
Map.find( ... );
Map.erase( ... )...
Thread 1
Set.find( ... );
Map.insert( ... );
Set.find( ... );
Set.insert( ... );
Thread N
Эпоха 1
RCU.sync() - ждем, пока все потоки покинут эпоху 1
Set.find( ... );
Map.insert( ... );
Set.find( ... );
Set.insert( ... );
... Map.erase;
Map.find( ... );
Set.insert( ... );
Map.find( ... );
++Эпоха
Эпоха 2
Максим Хижинский, CoreHard.by Минск, 2016
Lock-free listLock-free list
Максим Хижинский, CoreHard.by Минск, 2016
Atomic примитивы
●
atomic load/store
● atomic CAS
Safe memory
reclamation schema
(Hazard Pointers, uRCU)+Решили:
●
Проблему удаления узлов (delete)
●
ABA-проблему
Открытая проблема: параллельный insert и erase
Lock-free list: insert/eraseLock-free list: insert/erase
A: find key 3
H T52 8
prev
3
found
B: find insert pos for key 4
iprev inext
A: erase key 3
H T52 83
prev->next_.CAS( found, next )
next
B: insert key 4
H T52 83
4
iprev->next_.CAS( inext, new_item )
local vars
Максим Хижинский, CoreHard.by Минск, 2016
Marked pointerMarked pointer
[ T.Harris, 2001 ]
Двухфазное удаление:
● Логическое удаление — помечаем элемент
●
Физическое удаление — исключаем элемент
В качестве метки используем младший бит указателя
Максим Хижинский, CoreHard.by Минск, 2016
Lock-free list: marked pointerLock-free list: marked pointer
H T52 8
prev
3
found
iprev inext
nextA: erase
B: insert
A: Logical deletion - mark item found
H T52 83
found->next_.CAS( next, next | 1 )
B: iprev->next_.CAS( inext, new_item ) - failed!!!
A: Physical deletion - unlink item found
H T52 83
prev->next_.CAS( found, next )
Максим Хижинский, CoreHard.by Минск, 2016
ИтераторыИтераторы
11 Выдача итератора наружу — фактически,
ссылки на элемент списка
но - элемент в любой момент может быть
удален
Требования и контраргументы
for (auto it = list.begin(); it != list.end(); ++it)
it->do_something();
Максим Хижинский, CoreHard.by Минск, 2016
guarded_ptrguarded_ptr
template <typename T>
struct guarded_ptr {
hp_guard hp; // защищает элемент
T * ptr; // элемент списка
guarded_ptr(std::atomic<T *>& p) {
ptr = hp.protect( p ); }
~guarded_ptr() { hp.clear(); }
T * operator ->() const { return ptr; }
explicit operator bool() const
{ return ptr != nullptr; }
};
// Пример
guarded_ptr gp = list.find( key );
if ( gp ) {
// можно безопасно обращаться к полям T через gp
}
Максим Хижинский, CoreHard.by Минск, 2016
ИтераторыИтераторы
но - список постоянно изменяется
22Обход всех элементов списка
for (auto it = list.begin(); it != list.end(); ++it)
it->do_something();
Требования и контраргументы
Максим Хижинский, CoreHard.by Минск, 2016
ИтераторыИтераторы
auto it = list.begin() it == list.end()
++it; ... ++it;
H T
inserted
removed
stable
Максим Хижинский, CoreHard.by Минск, 2016
ИтераторыИтераторы
H T
H T
deleted
мусор!
it
it
Максим Хижинский, CoreHard.by Минск, 2016
Iterable lock-free listIterable lock-free list
H T
2 4 5 8
Элементы списка хранят указатели на данные
При удалении ключа обнуляется указатель, сам элемент списка остается
H T
2 4 5 8
struct node {
std::atomic<node *> next;
std::atomic<T *> data;
};Максим Хижинский, CoreHard.by Минск, 2016
Iterable lock-free listIterable lock-free list
class iterator {
node * node;
guarded_ptr<T> data; // текущий элемент
public:
iterator& operator++() {
while ( node ) {
node = node->next.load(); // не требует защиты
if ( !node ) break;
data.ptr = data.hp.protect(node->data.load());
if ( data.ptr ) break;
}
return *this;
}
T* operator ->() { return data.ptr; }
T& operator *() { return *data.ptr; }
};
Максим Хижинский, CoreHard.by Минск, 2016
Спасибо за внимание!
https://github.com/khizmax/libcds
Максим Хижинский, CoreHard.by Минск, 2016

Конкурентные ассоциативные контейнеры

  • 1.
    Максим Хижинский, CoreHard.byМинск, 2016 Lock-free maps изнутриLock-free maps изнутри
  • 2.
  • 3.
  • 4.
    Lock-free ordered listLock-freeordered list Операции: ● insert( node ) ● erase( key ) ● find( key ) template <class T> struct node { std::atomic<node*> next_; T data_; }; H T52 8 Lock-free примитивы: ● atomic load/store ● atomic compare-and-swap (CAS) Максим Хижинский, CoreHard.by Минск, 2016
  • 5.
    CAS — compare-and-swapCAS— compare-and-swap template <typename T> bool CAS( T * pAtomic, T expected, T desired ) atomically { if ( *pAtomic == expected ) { *pAtomic = desired; return true; } else return false; }; bool std::atomic<T>::compare_exchange( T& expected, T desired ); Максим Хижинский, CoreHard.by Минск, 2016
  • 6.
    Lock-free list: insertLock-freelist: insert H T52 8 3 H T52 8 3 3. prev->next_.CAS( next, new_node ) 1. find insert position for key 3 2. new_node.next_.store( next ) H T52 8 prev next new_node Максим Хижинский, CoreHard.by Минск, 2016
  • 7.
    Local varsLocal vars HT52 8 prev 3 found erase( Key k ) { retry: node * prev = Head; do { node * found = prev->next_.load(); if ( found->key == k ) { node * next = found->next_.load(); if (prev->next_.CAS( found, next )) { delete found; return true; } else goto retry; } prev = found; } while ( found->key < k ); return false; } Проблема: локальные ссылки next Максим Хижинский, CoreHard.by Минск, 2016
  • 8.
    ABA-проблемаABA-проблема 52 prev 3 found next Thread A:erase(3) Thread B 52 3 erase(3); erase(5) 2 3 insert(4) Heap new node(4) alloc delete 42 preempted... 42 prev found next 5 addr(3) == addr(4) prev->next_.CAS( found, next ) - success!!! 2 мусор 5 Максим Хижинский, CoreHard.by Минск, 2016
  • 9.
    Tagged pointersTagged pointers pointertag prev->next_.dwCAS( found, {next.ptr, prev->next_.tag + 1} ) template <class T> struct tagged_ptr { T * ptr; uintptr_t tag; }; Требует dwCAS — не везде есть Решает только ABA-проблему Освободить память нельзя, нужен free-list [ boost.lock-free ] H T52 8 prev 3 found next Максим Хижинский, CoreHard.by Минск, 2016
  • 10.
    Hazard pointersHazard pointers HT52 8 prev 3 found erase( Key k ) { hp_guard hp1 = get_guard(); hp_guard hp2 = get_guard(); retry: node * prev = Head; do { node * found = hp2.protect( prev->next_); if ( found->key == k ) if (prev->next_.CAS( found, found->next_.load())) { hp_retire( found ); return true; } else goto retry; } hp1 = hp2; prev = found; } while ( found->key < k ); return false; } Распределяем HP (TLS) Защищаем элемент Отложенное удаление Максим Хижинский, CoreHard.by Минск, 2016
  • 11.
    Hazard PointersHazard Pointers ОбъявлениеHazard Pointer'а – защита локальной ссылки class hp_guard { void * hp; // ... }; T * hp_guard::protect(std::atomic<T*>& what) { T * t; do { hp = t = what.load(); } while (t != what.load()); return t; } 0 1 … K - 1 thread-local ● HP[K] Другой поток может изменить what, поэтому - цикл Максим Хижинский, CoreHard.by Минск, 2016
  • 12.
    Hazard PointersHazard Pointers Удалениеэлемента void hp_retire( T * what ) { push what to current_thread.Retired array if ( current_thread.Retired is full ) hp.Scan( current_thread ); } // сердце hazard pointer void hp::Scan( current_thread ) { void * guarded[K*P] = union HP[K] for all P thread; foreach ( p in current_thread.Retired[R] ) if ( p not in guarded[] ) delete p; } 0 1 2 … R - 1 Retired[R] 0 1 … K - 1 HP[K] thread-local Максим Хижинский, CoreHard.by Минск, 2016
  • 13.
    Hazard pointersHazard pointers ✔Использует только атомарные чтение/запись  Защищает только локальные ссылки ✔ Размер массива отложенных (готовых к удалению) элементов ограничен сверху  Перед работой с указателем его следует объявить как hazard решает ABA-проблему Физическое удаление элементов Максим Хижинский, CoreHard.by Минск, 2016
  • 14.
    Epoch-based SMREpoch-based SMR Map.find(... ); Set.insert( ... ); Map.find( ... ); Map.erase( ... )... Thread 1 Set.find( ... ); Map.insert( ... ); Set.find( ... ); Set.insert( ... ); Thread N Эпоха 1 RCU.sync() - ждем, пока все потоки покинут эпоху 1 Set.find( ... ); Map.insert( ... ); Set.find( ... ); Set.insert( ... ); ... Map.erase; Map.find( ... ); Set.insert( ... ); Map.find( ... ); ++Эпоха Эпоха 2 Максим Хижинский, CoreHard.by Минск, 2016
  • 15.
    Lock-free listLock-free list МаксимХижинский, CoreHard.by Минск, 2016 Atomic примитивы ● atomic load/store ● atomic CAS Safe memory reclamation schema (Hazard Pointers, uRCU)+Решили: ● Проблему удаления узлов (delete) ● ABA-проблему Открытая проблема: параллельный insert и erase
  • 16.
    Lock-free list: insert/eraseLock-freelist: insert/erase A: find key 3 H T52 8 prev 3 found B: find insert pos for key 4 iprev inext A: erase key 3 H T52 83 prev->next_.CAS( found, next ) next B: insert key 4 H T52 83 4 iprev->next_.CAS( inext, new_item ) local vars Максим Хижинский, CoreHard.by Минск, 2016
  • 17.
    Marked pointerMarked pointer [T.Harris, 2001 ] Двухфазное удаление: ● Логическое удаление — помечаем элемент ● Физическое удаление — исключаем элемент В качестве метки используем младший бит указателя Максим Хижинский, CoreHard.by Минск, 2016
  • 18.
    Lock-free list: markedpointerLock-free list: marked pointer H T52 8 prev 3 found iprev inext nextA: erase B: insert A: Logical deletion - mark item found H T52 83 found->next_.CAS( next, next | 1 ) B: iprev->next_.CAS( inext, new_item ) - failed!!! A: Physical deletion - unlink item found H T52 83 prev->next_.CAS( found, next ) Максим Хижинский, CoreHard.by Минск, 2016
  • 19.
    ИтераторыИтераторы 11 Выдача итераторанаружу — фактически, ссылки на элемент списка но - элемент в любой момент может быть удален Требования и контраргументы for (auto it = list.begin(); it != list.end(); ++it) it->do_something(); Максим Хижинский, CoreHard.by Минск, 2016
  • 20.
    guarded_ptrguarded_ptr template <typename T> structguarded_ptr { hp_guard hp; // защищает элемент T * ptr; // элемент списка guarded_ptr(std::atomic<T *>& p) { ptr = hp.protect( p ); } ~guarded_ptr() { hp.clear(); } T * operator ->() const { return ptr; } explicit operator bool() const { return ptr != nullptr; } }; // Пример guarded_ptr gp = list.find( key ); if ( gp ) { // можно безопасно обращаться к полям T через gp } Максим Хижинский, CoreHard.by Минск, 2016
  • 21.
    ИтераторыИтераторы но - списокпостоянно изменяется 22Обход всех элементов списка for (auto it = list.begin(); it != list.end(); ++it) it->do_something(); Требования и контраргументы Максим Хижинский, CoreHard.by Минск, 2016
  • 22.
    ИтераторыИтераторы auto it =list.begin() it == list.end() ++it; ... ++it; H T inserted removed stable Максим Хижинский, CoreHard.by Минск, 2016
  • 23.
  • 24.
    Iterable lock-free listIterablelock-free list H T 2 4 5 8 Элементы списка хранят указатели на данные При удалении ключа обнуляется указатель, сам элемент списка остается H T 2 4 5 8 struct node { std::atomic<node *> next; std::atomic<T *> data; };Максим Хижинский, CoreHard.by Минск, 2016
  • 25.
    Iterable lock-free listIterablelock-free list class iterator { node * node; guarded_ptr<T> data; // текущий элемент public: iterator& operator++() { while ( node ) { node = node->next.load(); // не требует защиты if ( !node ) break; data.ptr = data.hp.protect(node->data.load()); if ( data.ptr ) break; } return *this; } T* operator ->() { return data.ptr; } T& operator *() { return *data.ptr; } }; Максим Хижинский, CoreHard.by Минск, 2016
  • 26.