View
227
Download
0
Category
Preview:
Citation preview
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 8prev next
new_node
Максим Хижинский, CoreHard.by Минск, 2016
Local varsLocal varsH T52 8
prev3
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 3insert(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 8prev
3found next
Максим Хижинский, CoreHard.by Минск, 2016
Hazard pointersHazard pointersH T52 8
prev3
founderase( 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;
}
012…
R - 1
Retired[R]
01…
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/eraseA: find key 3
H T52 8prev
3found
B: find insert pos for key 4
iprev inextA: 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 pointerH T52 8
prev3
foundiprev inext
nextA: eraseB: 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_ptrtemplate <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
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 listclass 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
Recommended