2. Lock-free hash tableLock-free hash table
LIBCDS
Максим Хижинский, C++ Russia 2015
0 1 2 3 4 5 6
X X X
k1
k
k
k k
k
kk
k
k
2
3
4 5
6
7
8
9
T[8]
Lock-free
список
коллизий
7
k
10
3. Lock-free ordered listLock-free ordered list
LIBCDS
Максим Хижинский, C++ Russia 2015
Операции:
●
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)
4. CAS — compare-and-swapCAS — compare-and-swap
LIBCDS
Максим Хижинский, C++ Russia 2015
template <typename T>
bool CAS( T * pAtomic, T expected, T desired )
atomically {
if ( *pAtomic == expected ) {
*pAtomic = desired;
return true;
}
else
return false;
};
5. Lock-free list: insertLock-free list: insert
LIBCDS
Максим Хижинский, C++ Russia 2015
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
6. Lock-free list: eraseLock-free list: erase
LIBCDS
Максим Хижинский, C++ Russia 2015
1. find key 3
2. prev->next_.CAS( found, next )
H T52 8
prev
3
found
H T52 83
Проблема: параллельный insert
next
7. Lock-free list: insert/eraseLock-free list: insert/erase
LIBCDS
Максим Хижинский, C++ Russia 2015
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
8. Marked pointerMarked pointer
LIBCDS
Максим Хижинский, C++ Russia 2015
[ T.Harris, 2001 ]
Двухфазное удаление:
● Логическое удаление — помечаем элемент
●
Физическое удаление — исключаем элемент
В качестве метки используем младший бит указателя
9. Lock-free list: marked pointerLock-free list: marked pointer
LIBCDS
Максим Хижинский, C++ Russia 2015
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 - remove item found
H T52 83
prev->next_.CAS( found, next )
10. Lock-free list: problemsLock-free list: problems
LIBCDS
Максим Хижинский, C++ Russia 2015
H T52 8
prev
3
found
iprev inext
nextA: erase
B: insert
iprev->next_.CAS( inext, new_item )
prev->next_.CAS( found, next )
local vars
Вдруг уже удалены?..
11. Lock-free list: problemsLock-free list: problems
LIBCDS
Максим Хижинский, C++ Russia 2015
Проблемы:
●
Защита локальных данных — когда элемент можно
безопасно удалить?
●
ABA-проблема
12. ABA-проблемаABA-проблема
LIBCDS
Максим Хижинский, C++ Russia 2015
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 мусор
13. SMRSMR
LIBCDS
Максим Хижинский, C++ Russia 2015
Проблемы:
● Защита локальных данных — когда элемент можно
безопасно удалить?
●
ABA-проблема
Решение:
Safe memory reclamation (SMR)
● Tagged pointers
●
Hazard Pointers
●
User-space RCU
14. Tagged pointersTagged pointers
LIBCDS
Максим Хижинский, C++ Russia 2015
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
15. Tagged pointers: historyTagged pointers: history
LIBCDS
Максим Хижинский, C++ Russia 2015
ABA-проблема характерна только для CAS
Архитектуры процессоров
LL/SC:
●
IBM PowerPC
● MIPS
●
ARM
➢ LL — load linked
➢ SC — store conditional
bool weak_CAS( T * ptr,
T expected, T desired )
{ T cur = LL( ptr );
return cur == expected
&& SC( ptr, desired );
}
Эмуляция LL/SC на CAS
— намного труднее
CAS:
●
x86, amd64
● Sparc
●
Itanium
С++11 — только CAS
16. Hazard pointersHazard pointers
LIBCDS
Максим Хижинский, C++ Russia 2015
✔ Использует только атомарные чтение/запись
Защищает только локальные ссылки
✔ Размер массива отложенных (готовых к
удалению) элементов ограничен сверху
Перед работой с указателем его следует
объявить как hazard
решает ABA-проблему
Физическое удаление элементов
17. Hazard pointersHazard pointers
LIBCDS
Максим Хижинский, C++ Russia 2015
H T52 8
prev
3
found next
erase( Key k ) {
hp_guard h1 = get_guard();
hp_guard h2 = get_guard();
retry:
node * prev = Head;
do {
node * found = h2.protect( prev->next_);
if ( found->key == k )
if (prev->next_.CAS( found, found->next_)) {
hp_retire( found );
return true;
}
else
goto retry;
h1 = h2;
prev = found;
} while ( found->key < k );
return false;
}
Распределяем HP (TLS)
Защищаем элемент
Удаляем элемент
18. Hazard pointersHazard pointers
LIBCDS
Максим Хижинский, C++ Russia 2015
P – thread count
Thread 0
Thread HP Manager
0
1
…
K - 1
HP[K]
0
1
2
…
R - 1
Retired[R]
Hazard Pointer Singleton
Thread
1
Thread
P - 1
K = 4 R = 2 KP
<K,P, R> : R > K * P
19. Hazard PointersHazard Pointers
LIBCDS
Максим Хижинский, C++ Russia 2015
Объявление 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
HP[K]
20. Hazard PointersHazard Pointers
LIBCDS
Максим Хижинский, C++ Russia 2015
Удаление элемента
void hp_retire( T * what ) {
push what to current_thread.Retired array
if ( current_thread.Retired is full )
hp.Scan( current_thread );
}
void hp::Scan() {
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;
}
<K,P, R> : R > K * P
0
1
2
…
R - 1
Retired[R]
0
1
…
K - 1
HP[K]
21. User-space Read-Copy UpdateUser-space Read-Copy Update
LIBCDS
Максим Хижинский, C++ Russia 2015
✔ RCU — метод синхронизации:
RCU.lock() / RCU.unlock()
✔ Разработан для почти-read-only данных (map, set)
✔ Очень легкие read-side lock
✔ Удаление элемента — ожидание окончания эпохи
решает ABA-проблему
Физическое удаление элементов
RCU.lock(): ничего не блокирует
Объявляет, что поток входит в
текущую RCU-эпоху
23. Lock-free hash tableLock-free hash table
LIBCDS
Максим Хижинский, C++ Russia 2015
0 1 2 3 4 5 6
X X X
k1
k
k
k k
k
kk
k
k
2
3
4 5
6
7
8
9
T[8]
Lock-free
cписок:
HP/RCU
+ marked
pointers
7
k
10
Hash table + Lock-free ordered list
No rehashing
24. Split-ordered listSplit-ordered list
LIBCDS
Максим Хижинский, C++ Russia 2015
0 8 2 1 9 13
T[0]
T[1]
k iSentinel node Regular node
N = 2 size() = 4
Load factor L: size() / N ≤ L
Если L = 2, то вставка нового элемента приводит к увеличению
hash table
Hash table
Lock-free ordered list
25. Skip listSkip list
LIBCDS
Максим Хижинский, C++ Russia 2015
X
X
X
X
X
X
X
X10 15 23 34 5542
23
Tower
h = 3
Вероятностная структура данных:
P[ h == 1 ] = 1/2
P[ h == k ] = 1/2
k
, 0 < k < 32
h = lsb( rand() )
O(log(N))