无锁栈的几种实现方式
以下几种方式
● stack1 使用了最简单的 threads_in_pop 记录当前 pop 的线程数,控制并发访问安全
● stack2 使用了风险指针,对指涉指针进行保护
● stack3 使用 c++20的原子智能指针,减少了对内存的管理,十分简洁
● stack4 使用了两层内外指针,分别标志使用数和完成数,解决了 ABA 问题。
#include <atomic>
#include <functional>
#include <memory>
#include <thread>
template <typename T>
class stack_lock_free {
private:
struct node {
std::shared_ptr<T> data;
node* next;
node(T const& value)
: data(std::make_shared<T>(value))
, next(nullptr)
{
}
};
std::atomic<node*> head;
std::atomic<node*> to_be_deleted;
std::atomic<unsigned> threads_in_pop;
static void delete_nodes(node* nodes)
{
while (nodes) {
node* next = nodes->next;
delete nodes;
nodes = next;
}
}
void try_reclain(node* old_head)
{
if (threads_in_pop == 1) {
node* nodes_to_delete = to_be_deleted.exchange(nullptr);
if (!--threads_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;
}
}
void chain_pending_nodes(node* nodes)
{
node* last = nodes;
while (last->next) {
last = last->next;
}
chain_pending_nodes(nodes, last);
}
void chain_pending_nodes(node* first, node* last)
{
last->next = to_be_deleted.load();
while (!to_be_deleted.compare_exchange_weak(last->next, first))
;
}
void chain_pending_node(node* node)
{
chain_pending_nodes(node, node);
}
public:
stack_lock_free() { }
~stack_lock_free()
{
while (pop()) { }
node* to_delete = to_be_deleted.exchange(nullptr);
delete_nodes(to_delete);
}
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_reclain(old_head);
return res;
}
void push(T const& value)
{
node* new_node = new node(value);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next, new_node))
;
}
};
/* Hazard pointers implementation */
unsigned const max_hazard_pointers = 100;
struct hazard_pointer {
std::atomic<void*> pointer;
std::atomic<std::thread::id> id;
};
inline hazard_pointer hazard_pointers[max_hazard_pointers];
class hp_owner {
private:
hazard_pointer* hp;
public:
hp_owner(hp_owner const&) = delete;
hp_owner& operator=(hp_owner const&) = delete;
hp_owner()
: hp(nullptr)
{
for (unsigned 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 pointers available");
}
}
std::atomic<void*>& get_pointer()
{
return hp->pointer;
}
~hp_owner()
{
hp->pointer.store(nullptr);
hp->id.store(std::thread::id());
}
};
inline std::atomic<void*>& get_hazard_pointer_for_current_thread()
{
thread_local static hp_owner hazard;
return hazard.get_pointer();
}
inline bool outstanding_hazard_pointers_for(void* p)
{
for (unsigned i = 0; i < max_hazard_pointers; ++i) {
if (hazard_pointers[i].pointer.load() == p) {
return true;
}
}
return false;
}
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(nullptr)
{
}
~data_to_reclaim()
{
deleter(data);
}
};
inline std::atomic<data_to_reclaim*> nodes_to_reclaim;
inline 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>
inline void reclaim_later(T* data)
{
add_to_reclaim_list(new data_to_reclaim(data));
}
inline 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;
}
}
template <typename T>
class stack_lock_free2 {
private:
struct node {
std::shared_ptr<T> data;
node* next;
node(T const& value)
: data(std::make_shared<T>(value))
, next(nullptr)
{
}
};
std::atomic<node*> head;
std::atomic<node*> to_be_deleted;
static void delete_nodes(node* nodes)
{
while (nodes) {
node* next = nodes->next;
delete nodes;
nodes = next;
}
}
public:
stack_lock_free2() { }
~stack_lock_free2()
{
while (pop()) { }
node* to_delete = to_be_deleted.exchange(nullptr);
delete_nodes(to_delete);
}
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);
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;
}
void push(T const& value)
{
node* new_node = new node(value);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next, new_node))
;
}
};
/* Lock-free stack implementation of reference counted objects */
template <typename T>
class stack_lock_free3 {
private:
struct node {
std::shared_ptr<T> data;
std::atomic<std::shared_ptr<node>> next;
node(T const& value)
: data(std::make_shared<T>(value))
, next(nullptr)
{
}
};
public:
std::atomic<std::shared_ptr<node>> head;
void push(T const& value)
{
std::shared_ptr<node> const new_node = std::make_shared<node>(value);
new_node->next.store(head.load());
std::shared_ptr<node> excepted = new_node->next.load();
while (!head.compare_exchange_weak(excepted, new_node))
;
}
std::shared_ptr<T> pop()
{
std::shared_ptr<node> old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next.load()))
;
return old_head ? old_head->data : std::shared_ptr<T>();
}
~stack_lock_free3()
{
while (pop()) { }
}
};
/* Lock-free stack implementation of internal and external references */
template <typename T>
class stack_lock_free4 {
private:
struct node;
struct counted_node_ptr {
int external_count;
node* ptr;
};
struct node {
std::atomic<int> internal_count;
std::shared_ptr<T> data;
std::shared_ptr<counted_node_ptr> next;
node(T const& value)
: internal_count(0)
, data(std::make_shared<T>(value))
{
}
};
std::atomic<std::shared_ptr<counted_node_ptr>> head;
public:
~stack_lock_free4()
{
while (pop()) { }
};
stack_lock_free4()
{
head.store(std::make_shared<counted_node_ptr>());
}
void push(T const& value)
{
std::shared_ptr<counted_node_ptr> new_node = std::make_shared<counted_node_ptr>();
new_node->ptr = new node(value);
new_node->external_count = 1;
new_node->ptr->next = head.load();
while (!head.compare_exchange_weak(new_node->ptr->next, new_node))
;
}
std::shared_ptr<T> pop()
{
std::shared_ptr<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;
}
}
}
void increase_head_count(std::shared_ptr<counted_node_ptr>& old_counter)
{
std::shared_ptr<counted_node_ptr> new_counter = std::make_shared<counted_node_ptr>();
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;
}
};

浙公网安备 33010602011771号