26
Максим Хижинский, CoreHard.by Минск, 2016 Lock-free maps изнутри Lock-free maps изнутри

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

Embed Size (px)

Citation preview

Максим Хижинский, 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 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

ИтераторыИтераторы

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

Спасибо за внимание!

https://github.com/khizmax/libcds

Максим Хижинский, CoreHard.by Минск, 2016