Run clang-format

This commit is contained in:
Bassem Girgis
2018-10-18 00:40:34 -05:00
parent fffe4528da
commit 31c32e368a
149 changed files with 5974 additions and 6287 deletions

View File

@@ -1,63 +1,53 @@
#include <cstdio>
#include <memory>
#include <thread>
#include <cstdio>
unsigned const max_hazard_pointers = 100;
struct hazard_pointer
{
std::atomic<std::thread::id> id;
std::atomic<void*> pointer;
struct hazard_pointer {
std::atomic<std::thread::id> id;
std::atomic<void*> pointer;
};
hazard_pointer hazard_pointers[max_hazard_pointers];
class hp_owner
{
hazard_pointer* hp;
class hp_owner {
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;
// try to claim ownership of a hazard pointer
if (hazard_pointers[i].id.compare_exchange_strong(
old_id, std::this_thread::get_id())) {
// successfully claimed the entry for the current thread,
// store it and stop the search
hp = &hazard_pointers[i];
printf("hp: %ld\n", hp);
break;
}
// another threads owns this entry, move on to the next
}
// if you get to the end of the list without finding a free entry,
// there are too many threads using hazard pointers, so throw an
// exception
if (!hp) {
throw std::runtime_error("No hazard pointers available");
}
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;
// try to claim ownership of a hazard pointer
if (hazard_pointers[i].id.compare_exchange_strong(
old_id, std::this_thread::get_id())) {
// successfully claimed the entry for the current thread,
// store it and stop the search
hp = &hazard_pointers[i];
printf("hp: %ld\n", hp);
break;
}
// another threads owns this entry, move on to the next
}
// if you get to the end of the list without finding a free entry,
// there are too many threads using hazard pointers, so throw an
// exception
if (!hp) {
throw std::runtime_error("No hazard pointers available");
}
}
std::atomic<void*>& get_pointer()
{
return hp->pointer;
}
std::atomic<void*>& get_pointer() { return hp->pointer; }
~hp_owner()
{
// when each thread exits, if an instance of hp_owner was created
// for the thread, then it's destryoed. The destructor then resets
// the actual pointer to nullptr before setting the owner ID to
// std::thread::id(), allowing another thread to reuse the entry later.
// TODO: this causes crash
//hp->pointer.store(nullptr);
//hp->id.store(std::thread::id());
}
~hp_owner()
{
// when each thread exits, if an instance of hp_owner was created
// for the thread, then it's destryoed. The destructor then resets
// the actual pointer to nullptr before setting the owner ID to
// std::thread::id(), allowing another thread to reuse the entry later.
// TODO: this causes crash
// hp->pointer.store(nullptr);
// hp->id.store(std::thread::id());
}
};

View File

@@ -1,6 +1,6 @@
#include <iostream>
#include <cstdio>
#include <atomic>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
@@ -9,232 +9,210 @@
// mpmc queue
template <typename T>
class queue
{
private:
struct node;
struct counted_node_ptr
class queue {
private:
struct node;
struct counted_node_ptr {
int external_count;
node* ptr;
};
std::atomic<counted_node_ptr> head;
std::atomic<counted_node_ptr> tail;
struct node_counter {
unsigned internal_count : 30;
unsigned external_counters : 2;
// you need only 2 bits because there are at most two such
// counters (next and tail)
};
struct node {
std::atomic<T*> data;
std::atomic<node_counter> count;
std::atomic<counted_node_ptr> next;
node()
{
int external_count;
node* ptr;
};
node_counter new_count;
new_count.internal_count = 0;
new_count.external_counters = 2;
// because every node starts out referenced from tail and
// from the next pointer of the previous node once you've
// actually aadded it to the queue
count.store(new_count);
std::atomic<counted_node_ptr> head;
std::atomic<counted_node_ptr> tail;
struct node_counter
{
unsigned internal_count:30;
unsigned external_counters:2;
// you need only 2 bits because there are at most two such
// counters (next and tail)
};
struct node
{
std::atomic<T*> data;
std::atomic<node_counter> count;
std::atomic<counted_node_ptr> next;
node()
{
node_counter new_count;
new_count.internal_count = 0;
new_count.external_counters = 2;
// because every node starts out referenced from tail and
// from the next pointer of the previous node once you've
// actually aadded it to the queue
count.store(new_count);
counted_node_ptr new_next;
new_next.ptr = nullptr;
new_next.external_count = 0;
next.store(new_next);
}
void release_ref()
{
node_counter old_counter=
count.load(std::memory_order_relaxed);
node_counter new_counter;
do
{
new_counter=old_counter;
--new_counter.internal_count;
}
while(!count.compare_exchange_strong(
old_counter,new_counter,
std::memory_order_acquire,std::memory_order_relaxed));
if(!new_counter.internal_count &&
!new_counter.external_counters)
{
delete this;
}
}
};
void set_new_tail(counted_node_ptr &old_tail,
counted_node_ptr const &new_tail)
{
node* const current_tail_ptr = old_tail.ptr;
while(!tail.compare_exchange_weak(old_tail,new_tail) &&
old_tail.ptr == current_tail_ptr);
if(old_tail.ptr == current_tail_ptr)
free_external_counter(old_tail);
else
current_tail_ptr->release_ref();
counted_node_ptr new_next;
new_next.ptr = nullptr;
new_next.external_count = 0;
next.store(new_next);
}
static void increase_external_count(
std::atomic<counted_node_ptr>& counter,
counted_node_ptr& old_counter)
void release_ref()
{
counted_node_ptr new_counter;
do
{
new_counter = old_counter;
++new_counter.external_count;
}
while(!counter.compare_exchange_strong(
old_counter,new_counter,
std::memory_order_acquire,std::memory_order_relaxed));
old_counter.external_count = new_counter.external_count;
node_counter old_counter = count.load(std::memory_order_relaxed);
node_counter new_counter;
do {
new_counter = old_counter;
--new_counter.internal_count;
} while (!count.compare_exchange_strong(old_counter,
new_counter,
std::memory_order_acquire,
std::memory_order_relaxed));
if (!new_counter.internal_count && !new_counter.external_counters) {
delete this;
}
}
};
static void free_external_counter(counted_node_ptr &old_node_ptr)
{
node* const ptr = old_node_ptr.ptr;
int const count_increase = old_node_ptr.external_count-2;
node_counter old_counter =
ptr->count.load(std::memory_order_relaxed);
node_counter new_counter;
do
{
new_counter=old_counter;
--new_counter.external_counters;
new_counter.internal_count+=count_increase;
}
while(!ptr->count.compare_exchange_strong(
old_counter,new_counter,
std::memory_order_acquire,std::memory_order_relaxed));
if(!new_counter.internal_count &&
!new_counter.external_counters)
{
delete ptr;
}
void set_new_tail(counted_node_ptr& old_tail,
counted_node_ptr const& new_tail)
{
node* const current_tail_ptr = old_tail.ptr;
while (!tail.compare_exchange_weak(old_tail, new_tail) &&
old_tail.ptr == current_tail_ptr)
;
if (old_tail.ptr == current_tail_ptr)
free_external_counter(old_tail);
else
current_tail_ptr->release_ref();
}
static void increase_external_count(std::atomic<counted_node_ptr>& counter,
counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
do {
new_counter = old_counter;
++new_counter.external_count;
} while (!counter.compare_exchange_strong(old_counter,
new_counter,
std::memory_order_acquire,
std::memory_order_relaxed));
old_counter.external_count = new_counter.external_count;
}
static void free_external_counter(counted_node_ptr& old_node_ptr)
{
node* const ptr = old_node_ptr.ptr;
int const count_increase = old_node_ptr.external_count - 2;
node_counter old_counter = ptr->count.load(std::memory_order_relaxed);
node_counter new_counter;
do {
new_counter = old_counter;
--new_counter.external_counters;
new_counter.internal_count += count_increase;
} while (!ptr->count.compare_exchange_strong(old_counter,
new_counter,
std::memory_order_acquire,
std::memory_order_relaxed));
if (!new_counter.internal_count && !new_counter.external_counters) {
delete ptr;
}
}
public:
queue()
: head()
, tail()
{}
public:
queue() : head(), tail() {}
queue(const queue& other)=delete;
queue& operator=(const queue& other)=delete;
queue(const queue& other) = delete;
queue& operator=(const queue& other) = delete;
~queue()
{
/*
while (node* const old_head = head.load()) {
head.store(old_head->next);
delete old_head;
}
*/
~queue()
{
/*
while (node* const old_head = head.load()) {
head.store(old_head->next);
delete old_head;
}
*/
}
void push(T new_value)
{
std::unique_ptr<T> new_data(new T(new_value));
counted_node_ptr new_next;
new_next.ptr = new node;
new_next.external_count=1;
counted_node_ptr old_tail=tail.load();
for(;;)
{
increase_external_count(tail,old_tail);
T* old_data=nullptr;
if(old_tail.ptr->data.compare_exchange_strong(
old_data,new_data.get()))
{
counted_node_ptr old_next={0};
if(!old_tail.ptr->next.compare_exchange_strong(
old_next,new_next))
{
delete new_next.ptr;
new_next=old_next;
}
set_new_tail(old_tail, new_next);
new_data.release();
break;
}
else
{
counted_node_ptr old_next={0};
if(old_tail.ptr->next.compare_exchange_strong(
old_next,new_next))
{
old_next=new_next;
new_next.ptr=new node;
}
set_new_tail(old_tail, old_next);
}
void push(T new_value)
{
std::unique_ptr<T> new_data(new T(new_value));
counted_node_ptr new_next;
new_next.ptr = new node;
new_next.external_count = 1;
counted_node_ptr old_tail = tail.load();
for (;;) {
increase_external_count(tail, old_tail);
T* old_data = nullptr;
if (old_tail.ptr->data.compare_exchange_strong(old_data,
new_data.get())) {
counted_node_ptr old_next = {0};
if (!old_tail.ptr->next.compare_exchange_strong(old_next, new_next)) {
delete new_next.ptr;
new_next = old_next;
}
}
std::unique_ptr<T> pop() {
counted_node_ptr old_head = head.load(std::memory_order_relaxed);
for(;;) {
increase_external_count(head, old_head);
node* const ptr = old_head.ptr;
if (ptr==tail.load().ptr) {
ptr->release_ref();
return std::unique_ptr<T>();
}
counted_node_ptr next = ptr->next.load();
if (head.compare_exchange_strong(old_head, next)) {
T* const res=ptr->data.exchange(nullptr);
free_external_counter(old_head);
return std::unique_ptr<T>(res);
}
ptr->release_ref();
set_new_tail(old_tail, new_next);
new_data.release();
break;
}
else {
counted_node_ptr old_next = {0};
if (old_tail.ptr->next.compare_exchange_strong(old_next, new_next)) {
old_next = new_next;
new_next.ptr = new node;
}
set_new_tail(old_tail, old_next);
}
}
}
std::unique_ptr<T> pop()
{
counted_node_ptr old_head = head.load(std::memory_order_relaxed);
for (;;) {
increase_external_count(head, old_head);
node* const ptr = old_head.ptr;
if (ptr == tail.load().ptr) {
ptr->release_ref();
return std::unique_ptr<T>();
}
counted_node_ptr next = ptr->next.load();
if (head.compare_exchange_strong(old_head, next)) {
T* const res = ptr->data.exchange(nullptr);
free_external_counter(old_head);
return std::unique_ptr<T>(res);
}
ptr->release_ref();
}
}
};
void push(queue<int>* q)
void
push(queue<int>* q)
{
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
q->push(i);
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
q->push(i);
}
}
void
pop(queue<int>* q)
{
int i = 0;
while (i < 10) {
std::shared_ptr<int> p = q->pop();
if (p) {
printf("poping %d\n", *p);
++i;
}
}
}
void pop(queue<int>* q)
int
main()
{
int i = 0;
while (i < 10) {
std::shared_ptr<int> p = q->pop();
if (p) {
printf("poping %d\n", *p);
++i;
}
}
queue<int> q;
std::thread t1(push, &q);
std::thread t2(pop, &q);
t1.join();
t2.join();
return 0;
}
int main()
{
queue<int> q;
std::thread t1(push, &q);
std::thread t2(pop, &q);
t1.join();
t2.join();
return 0;
}

View File

@@ -1,102 +1,98 @@
#include <iostream>
#include <cstdio>
#include <atomic>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
template <typename T>
class queue
{
private:
struct node
{
std::shared_ptr<T> data;
node* next;
class queue {
private:
struct node {
std::shared_ptr<T> data;
node* next;
node()
: next(nullptr)
{}
};
node() : next(nullptr) {}
};
std::atomic<node*> head;
std::atomic<node*> tail;
node* pop_head()
{
node* const old_head = head.load();
if (old_head == tail.load()) {
return nullptr;
}
head.store(old_head->next);
return old_head;
std::atomic<node*> head;
std::atomic<node*> tail;
node* pop_head()
{
node* const old_head = head.load();
if (old_head == tail.load()) {
return nullptr;
}
public:
queue()
: head(new node)
, tail(head.load())
{}
head.store(old_head->next);
return old_head;
}
queue(const queue& other)=delete;
queue& operator=(const queue& other)=delete;
public:
queue() : head(new node), tail(head.load()) {}
~queue()
{
while (node* const old_head = head.load()) {
head.store(old_head->next);
delete old_head;
}
queue(const queue& other) = delete;
queue& operator=(const queue& other) = delete;
~queue()
{
while (node* const old_head = head.load()) {
head.store(old_head->next);
delete old_head;
}
}
std::shared_ptr<T> pop()
{
node* old_head = pop_head();
if (!old_head) {
return std::shared_ptr<T>();
}
std::shared_ptr<T> pop()
{
node* old_head = pop_head();
if (!old_head) {
return std::shared_ptr<T>();
}
std::shared_ptr<T> const res(old_head->data);
delete old_head;
return res;
}
std::shared_ptr<T> const res(old_head->data);
delete old_head;
return res;
}
void push(T new_value)
{
std::shared_ptr<T> new_data(std::make_shared<T>(new_value));
node* p = new node;
node* const old_tail = tail.load();
old_tail->data.swap(new_data);
old_tail->next = p;
tail.store(p);
}
void push(T new_value)
{
std::shared_ptr<T> new_data(std::make_shared<T>(new_value));
node* p = new node;
node* const old_tail = tail.load();
old_tail->data.swap(new_data);
old_tail->next = p;
tail.store(p);
}
};
void push(queue<int>* q)
void
push(queue<int>* q)
{
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
q->push(i);
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
q->push(i);
}
}
void
pop(queue<int>* q)
{
int i = 0;
while (i < 10) {
std::shared_ptr<int> p = q->pop();
if (p) {
printf("poping %d\n", *p);
++i;
}
}
}
void pop(queue<int>* q)
int
main()
{
int i = 0;
while (i < 10) {
std::shared_ptr<int> p = q->pop();
if (p) {
printf("poping %d\n", *p);
++i;
}
}
queue<int> q;
std::thread t1(push, &q);
std::thread t2(pop, &q);
t1.join();
t2.join();
return 0;
}
int main()
{
queue<int> q;
std::thread t1(push, &q);
std::thread t2(pop, &q);
t1.join();
t2.join();
return 0;
}

View File

@@ -7,168 +7,161 @@
// to_be_deleted list would grow without bounds, and you'd be essentailly
// leaking memory again. If there aren't going to be any quiescent periods,
// you need to find an alternative mechanism for reclaming the nodes. The key
// is to identify when no more threads are accesing a particular node so that
// is to identify when no more threads are accesing a particular node so that
// it can reclaimed. By far the easiest such mechanism to reason about is the
// use of hazard ponters.
#include <iostream>
#include <cstdio>
#include <atomic>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
template <typename T>
class stack
{
private:
struct node
{
std::shared_ptr<T> data;
node *next;
class stack {
private:
struct node {
std::shared_ptr<T> data;
node* next;
node(const T& data_)
: data(std::make_shared<T>(data_))
{}
};
node(const T& data_) : data(std::make_shared<T>(data_)) {}
};
std::atomic<node *> head;
std::atomic<unsigned> threads_in_pop;
std::atomic<node *> to_be_deleted;
std::atomic<node*> head;
std::atomic<unsigned> threads_in_pop;
std::atomic<node*> to_be_deleted;
static void delete_nodes(node *nodes)
{
while (nodes) {
node *next = nodes->next;
delete nodes;
nodes = next;
}
static void delete_nodes(node* nodes)
{
while (nodes) {
node* next = nodes->next;
delete nodes;
nodes = next;
}
}
void try_reclaim(node* old_head)
{
if (threads_in_pop == 1) {
// claim list of to-be-deleted nodes
node* nodes_to_delete = to_be_deleted.exchange(nullptr);
void try_reclaim(node* old_head)
{
if (threads_in_pop == 1) {
// claim list of to-be-deleted nodes
node* nodes_to_delete = to_be_deleted.exchange(nullptr);
// are you the only thread in pop()?
if (!--threads_in_pop) {
// on other thread can be accessing this list of pending nodes.
// There may be new pending nodes, but you're not bothered
// about them for now, as long as it's safe to reclaim your
// list.
delete_nodes(nodes_to_delete);
}
else if (nodes_to_delete) {
// not safe to reclaim the nodes, so if there are any,
// you must chain them back onto the list of nodes
// pending deletion.
// This can happen if there are multiple threads accessing the
// data structure concurrently. Other threads might have
// called pop() in between the first tet of thread_in_pop and
// the "claiming" of the list, potentially adding new nodes to
// the list that are still being accesed by one or more of
// those other threads.
chain_pending_nodes(nodes_to_delete);
}
delete old_head;
}
else {
// not safe to delete any nodes, add the node to the pending list
chain_pending_node(old_head);
--threads_in_pop;
}
// are you the only thread in pop()?
if (!--threads_in_pop) {
// on other thread can be accessing this list of pending nodes.
// There may be new pending nodes, but you're not bothered
// about them for now, as long as it's safe to reclaim your
// list.
delete_nodes(nodes_to_delete);
}
else if (nodes_to_delete) {
// not safe to reclaim the nodes, so if there are any,
// you must chain them back onto the list of nodes
// pending deletion.
// This can happen if there are multiple threads accessing the
// data structure concurrently. Other threads might have
// called pop() in between the first tet of thread_in_pop and
// the "claiming" of the list, potentially adding new nodes to
// the list that are still being accesed by one or more of
// those other threads.
chain_pending_nodes(nodes_to_delete);
}
delete old_head;
}
void chain_pending_nodes(node* nodes)
{
node* last = nodes;
// traverse the chain to find the end
while (node* const next = last->next) {
last = next;
}
chain_pending_nodes(nodes, last);
else {
// not safe to delete any nodes, add the node to the pending list
chain_pending_node(old_head);
--threads_in_pop;
}
}
void chain_pending_nodes(node* first, node* last)
{
// replace the next pointer from the last node with
// the current to_be_deleted pointer
last->next = to_be_deleted;
// store the first node in the chain as the new to_be_deleted pointer
// have to use compare_exchange_weak in a loop here in order to ensure
// that you don't leak any nodes that have been added by another thread
while (!to_be_deleted.compare_exchange_weak(
last->next,first));
void chain_pending_nodes(node* nodes)
{
node* last = nodes;
// traverse the chain to find the end
while (node* const next = last->next) {
last = next;
}
chain_pending_nodes(nodes, last);
}
void chain_pending_node(node* n)
{
// adding a single node onto the list is a special case where the
// first node onto the list is a special case where the first node
// in the chain to be added is the same as the last one.
chain_pending_nodes(n,n);
}
public:
stack()
: head(nullptr)
, threads_in_pop(0)
, to_be_deleted(nullptr)
{}
void push(const T& data)
{
node *const new_node = new node(data);
new_node->next = head.load();
// loop to gurantee that last->next is correct
while (!head.compare_exchange_weak(new_node->next, new_node));
}
std::shared_ptr<T> pop()
{
++threads_in_pop; // increase counter before doing anything else
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); // extract data from node rather than
// coping pointer
}
try_reclaim(old_head); // reclaim deleted nodes if you can
return res;
void chain_pending_nodes(node* first, node* last)
{
// replace the next pointer from the last node with
// the current to_be_deleted pointer
last->next = to_be_deleted;
// store the first node in the chain as the new to_be_deleted pointer
// have to use compare_exchange_weak in a loop here in order to ensure
// that you don't leak any nodes that have been added by another thread
while (!to_be_deleted.compare_exchange_weak(last->next, first))
;
}
void chain_pending_node(node* n)
{
// adding a single node onto the list is a special case where the
// first node onto the list is a special case where the first node
// in the chain to be added is the same as the last one.
chain_pending_nodes(n, n);
}
public:
stack() : head(nullptr), threads_in_pop(0), to_be_deleted(nullptr) {}
void push(const T& data)
{
node* const new_node = new node(data);
new_node->next = head.load();
// loop to gurantee that last->next is correct
while (!head.compare_exchange_weak(new_node->next, new_node))
;
}
std::shared_ptr<T> pop()
{
++threads_in_pop; // increase counter before doing anything else
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); // extract data from node rather than
// coping pointer
}
try_reclaim(old_head); // reclaim deleted nodes if you can
return res;
}
};
void push(stack<int>* s)
void
push(stack<int>* s)
{
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
}
}
void
pop(stack<int>* s)
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
}
void pop(stack<int>* s)
int
main()
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}
int main()
{
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}

View File

@@ -1,193 +1,191 @@
#include <iostream>
#include <cstdio>
#include <atomic>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
#include "hazard_pointer.h"
std::atomic<void*>& get_hazard_pointer_for_current_thread()
std::atomic<void*>&
get_hazard_pointer_for_current_thread()
{
// The first time each thread calls this function, a new instance of
// hp_owner is created. The constructor for this new instance then
// searchs through the table of owner/pointer pairs looking for an entry
// without an owner. It uses compare_exchange_strong() to check for an
// entry without an owner and claim it in one go.
//
// Once the hp_owner instance has been created for a given thread, further
// accesses are much faster because the pointer is cached, so the table
// doesn't have to be scanned again.
printf("get harzard pointer for current thread, thread id: %d\n", std::this_thread::get_id());
thread_local static hp_owner hazard;
return hazard.get_pointer();
// The first time each thread calls this function, a new instance of
// hp_owner is created. The constructor for this new instance then
// searchs through the table of owner/pointer pairs looking for an entry
// without an owner. It uses compare_exchange_strong() to check for an
// entry without an owner and claim it in one go.
//
// Once the hp_owner instance has been created for a given thread, further
// accesses are much faster because the pointer is cached, so the table
// doesn't have to be scanned again.
printf("get harzard pointer for current thread, thread id: %d\n",
std::this_thread::get_id());
thread_local static hp_owner hazard;
return hazard.get_pointer();
}
bool outstanding_hazard_pointers_for(void *p)
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;
}
for (unsigned i = 0; i < max_hazard_pointers; ++i) {
if (hazard_pointers[i].pointer.load() == p) {
return true;
}
return false;
}
return false;
}
template <typename T>
void do_delete(void* p)
void
do_delete(void* p)
{
delete static_cast<T*>(p);
delete static_cast<T*>(p);
}
struct data_to_reclaim
{
void* data;
std::function<void(void *)> deleter;
data_to_reclaim* next;
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(0)
{}
template <typename T>
data_to_reclaim(T* p) : data(p), deleter(&do_delete<T>), next(0)
{
}
~data_to_reclaim()
{
deleter(data);
}
~data_to_reclaim() { deleter(data); }
};
std::atomic<data_to_reclaim*> nodes_to_reclaim;
void add_to_reclaim_list(data_to_reclaim* node)
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>
void reclaim_later(T* data)
{
add_to_reclaim_list(new data_to_reclaim(data));
}
void delete_nodes_with_no_hazards()
{
// first claims the entire list of nodes to be reclaimed;
// ensures that this is the only thread trying to reclaim
// this particular set of nodes; other threads are now free
// to add futher nodes to the list or event try to reclaim
// them without impacting the operation of this thread.
data_to_reclaim* current = nodes_to_reclaim.exchange(nullptr);
while (current) {
data_to_reclaim* const next = current->next;
// check each node in turn to see if there are any outstanding
// hazard pointers.
if (!outstanding_hazard_pointers_for(current->data)) {
// if there aren't, delete the entry
delete current;
}
else {
// otherwise, just add the item back on the list for
// reclaiming later
add_to_reclaim_list(current);
}
current=next;
}
node->next = nodes_to_reclaim.load();
while (!nodes_to_reclaim.compare_exchange_weak(node->next, node))
;
}
template <typename T>
class stack
void
reclaim_later(T* data)
{
private:
struct node
{
std::shared_ptr<T> data;
node *next;
add_to_reclaim_list(new data_to_reclaim(data));
}
node(const T& data_)
: data(std::make_shared<T>(data_))
{}
};
void
delete_nodes_with_no_hazards()
{
// first claims the entire list of nodes to be reclaimed;
// ensures that this is the only thread trying to reclaim
// this particular set of nodes; other threads are now free
// to add futher nodes to the list or event try to reclaim
// them without impacting the operation of this thread.
data_to_reclaim* current = nodes_to_reclaim.exchange(nullptr);
std::atomic<node *> head;
while (current) {
data_to_reclaim* const next = current->next;
public:
stack()
: head(nullptr)
{}
void push(const T& data)
{
node *const new_node = new node(data);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next, new_node));
// check each node in turn to see if there are any outstanding
// hazard pointers.
if (!outstanding_hazard_pointers_for(current->data)) {
// if there aren't, delete the entry
delete current;
}
std::shared_ptr<T> pop()
{
std::atomic<void*>& hp = get_hazard_pointer_for_current_thread();
node* old_head = head.load();
do {
node* temp;
do { // loop until you've set the harzard pointer to head
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); // clear hazard pointer once you're finished
std::shared_ptr<T> res;
if (old_head) {
res.swap(old_head->data);
if (outstanding_hazard_pointers_for(old_head)) {
// check for hazard pointers referencing
// a node before you delete it
reclaim_later(old_head);
}
else {
delete old_head;
}
delete_nodes_with_no_hazards();
}
return res;
else {
// otherwise, just add the item back on the list for
// reclaiming later
add_to_reclaim_list(current);
}
current = next;
}
}
template <typename T>
class stack {
private:
struct node {
std::shared_ptr<T> data;
node* next;
node(const T& data_) : data(std::make_shared<T>(data_)) {}
};
std::atomic<node*> head;
public:
stack() : head(nullptr) {}
void push(const T& data)
{
node* const new_node = new node(data);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next, new_node))
;
}
std::shared_ptr<T> pop()
{
std::atomic<void*>& hp = get_hazard_pointer_for_current_thread();
node* old_head = head.load();
do {
node* temp;
do { // loop until you've set the harzard pointer to head
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); // clear hazard pointer once you're finished
std::shared_ptr<T> res;
if (old_head) {
res.swap(old_head->data);
if (outstanding_hazard_pointers_for(old_head)) {
// check for hazard pointers referencing
// a node before you delete it
reclaim_later(old_head);
}
else {
delete old_head;
}
delete_nodes_with_no_hazards();
}
return res;
}
};
void push(stack<int>* s)
void
push(stack<int>* s)
{
printf("starting push\n");
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
printf("starting push\n");
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
}
}
void
pop(stack<int>* s)
{
printf("starting pop\n");
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
}
void pop(stack<int>* s)
int
main()
{
printf("starting pop\n");
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
printf("creating stack\n");
stack<int> s;
printf("stack created\n");
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}
int main()
{
printf("creating stack\n");
stack<int> s;
printf("stack created\n");
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}

View File

@@ -1,183 +1,171 @@
#include <iostream>
#include <cstdio>
#include <atomic>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
template <typename T>
class stack
{
private:
struct node;
// the external count is kept alongside the poiner to the node and is
// increased every time the pointer is read. When the reader is finished
// with the node, it decraes the internal count. A simple operation that
// reads the pointer will thus leave the external count increaesd by one
// and the internal count decreased by one when it's finished.
//
// When the external count/pointer pairing is no longer required (that is,
// the node is no longer accessbile from a location accessible to multple
// threads), the internal count is increased by the value of the external
// count minus one and external counter is discarded. Once the internal
// count is equal to zero, there are no outstanding references to the node
// and it can be safely deleted.
struct counted_node_ptr
{
int external_count;
node *ptr;
};
class stack {
private:
struct node;
struct node
{
std::shared_ptr<T> data;
std::atomic_int internal_count;
counted_node_ptr next;
// the external count is kept alongside the poiner to the node and is
// increased every time the pointer is read. When the reader is finished
// with the node, it decraes the internal count. A simple operation that
// reads the pointer will thus leave the external count increaesd by one
// and the internal count decreased by one when it's finished.
//
// When the external count/pointer pairing is no longer required (that is,
// the node is no longer accessbile from a location accessible to multple
// threads), the internal count is increased by the value of the external
// count minus one and external counter is discarded. Once the internal
// count is equal to zero, there are no outstanding references to the node
// and it can be safely deleted.
struct counted_node_ptr {
int external_count;
node* ptr;
};
node(const T& data_)
: data(new T(data_))
, internal_count(0)
{}
};
struct node {
std::shared_ptr<T> data;
std::atomic_int internal_count;
counted_node_ptr next;
std::atomic<counted_node_ptr> head;
// NOTE: on those platforms that support a double-word-compare-and-swap
// operation, counted_node_ptr will be small enough for
// std::atomic<counted_node_ptr> to be lock-free. If it isn't on your
// platform, you might be better off using the std::shared_ptr<> version,
// because std::atomic<> will use a mutex to guarantee atomicity when the
// type is too large for the pltaform's atomic instrcutions (thus rending
// your "lock-free" algorithm lock-based after all). Alternatively, if
// you are're willing to limit the size of the counter, and you know that
// your platform has spare bits in a pointer (for example, because the
// address space is only 48 bits but a pointer is 64 bits), you can store
// the count inside the spare bits of the pointer to fit it all back in a
// single machine word.
node(const T& data_) : data(new T(data_)), internal_count(0) {}
};
std::atomic<counted_node_ptr> head;
// NOTE: on those platforms that support a double-word-compare-and-swap
// operation, counted_node_ptr will be small enough for
// std::atomic<counted_node_ptr> to be lock-free. If it isn't on your
// platform, you might be better off using the std::shared_ptr<> version,
// because std::atomic<> will use a mutex to guarantee atomicity when the
// type is too large for the pltaform's atomic instrcutions (thus rending
// your "lock-free" algorithm lock-based after all). Alternatively, if
// you are're willing to limit the size of the counter, and you know that
// your platform has spare bits in a pointer (for example, because the
// address space is only 48 bits but a pointer is 64 bits), you can store
// the count inside the spare bits of the pointer to fit it all back in a
// single machine word.
// once you've loaded the value of head, you must first increaes the count
// of external references to the head node to indicate that you're
// referencing it and to ensure that it's safe to deference it.
// By incrementing the external reference count, you ensure that the
// pointer remains valid for the duration of your access.
void increase_head_count(counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
// once you've loaded the value of head, you must first increaes the count
// of external references to the head node to indicate that you're
// referencing it and to ensure that it's safe to deference it.
// By incrementing the external reference count, you ensure that the
// pointer remains valid for the duration of your access.
void increase_head_count(counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
do
{
new_counter = old_counter;
++new_counter.external_count;
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;
}
public:
~stack()
{
while (pop())
;
}
void push(const T& data)
{
counted_node_ptr new_node;
new_node.ptr = new node(data);
// internal_count is zeor, and the external_count is one;
// because this is a new node, there's currently only one
// external reference to the node (the head pointer itself).
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()
{
counted_node_ptr old_head = head.load();
for (;;) {
increase_head_count(old_head);
node* ptr = old_head.ptr;
// if the pointer is a null pointer, you're at the end of list:
// no more entires
if (!ptr) {
return std::shared_ptr<T>();
}
// if the pointer isn't a null pointer, try to remove the node
if (head.compare_exchange_strong(old_head, ptr->next)) {
// you've taken the ownership of the node and can swap out
// the data in prepration for returning it.
std::shared_ptr<T> res;
res.swap(ptr->data);
// you've removed the node from the list, so you drop one
// off the count for that, and you're no longer accessing
// the node from this thread, so you drop another off the
// count for that.
const int count_increase = old_head.external_count - 2;
// if the reference count is now zero, the previous value
// (which is what fetch_add returns) was the negative of what
// you just added, in which case you can delete the node.
if (ptr->internal_count.fetch_add(count_increase) == -count_increase) {
delete ptr;
}
while (!head.compare_exchange_strong(old_counter, new_counter));
old_counter.external_count = new_counter.external_count;
}
public:
~stack()
{
while (pop());
}
void push(const T& data)
{
counted_node_ptr new_node;
new_node.ptr = new node(data);
// internal_count is zeor, and the external_count is one;
// because this is a new node, there's currently only one
// external reference to the node (the head pointer itself).
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()
{
counted_node_ptr old_head = head.load();
for (;;)
{
increase_head_count(old_head);
node* ptr = old_head.ptr;
// if the pointer is a null pointer, you're at the end of list:
// no more entires
if (!ptr)
{
return std::shared_ptr<T>();
}
// if the pointer isn't a null pointer, try to remove the node
if (head.compare_exchange_strong(old_head,ptr->next))
{
// you've taken the ownership of the node and can swap out
// the data in prepration for returning it.
std::shared_ptr<T> res;
res.swap(ptr->data);
// you've removed the node from the list, so you drop one
// off the count for that, and you're no longer accessing
// the node from this thread, so you drop another off the
// count for that.
const int count_increase = old_head.external_count - 2;
// if the reference count is now zero, the previous value
// (which is what fetch_add returns) was the negative of what
// you just added, in which case you can delete the node.
if (ptr->internal_count.fetch_add(count_increase) ==
-count_increase)
{
delete ptr;
}
// whether or not you deleted the node, you've finished.
return res;
}
// if the compare/exchange fails, another therad removed your node
// before you did, or another thread added a new node to the stack.
// Either way, you need start again with the fresh value of head
// returned by the compare/exchange call. But first you must
// decrease the reference count on the node you were trying to
// remove. This thread won't access it anymore. If you're the last
// thread to hold a reference (because another thread removed it
// from the stack), the internal reference count will be 1, so
// subtracing 1 will set the count to zero. In this case, you can
// delete the node here before you loop.
else if (ptr->internal_count.fetch_add(-1) == 1)
{
delete ptr;
}
}
// whether or not you deleted the node, you've finished.
return res;
}
// if the compare/exchange fails, another therad removed your node
// before you did, or another thread added a new node to the stack.
// Either way, you need start again with the fresh value of head
// returned by the compare/exchange call. But first you must
// decrease the reference count on the node you were trying to
// remove. This thread won't access it anymore. If you're the last
// thread to hold a reference (because another thread removed it
// from the stack), the internal reference count will be 1, so
// subtracing 1 will set the count to zero. In this case, you can
// delete the node here before you loop.
else if (ptr->internal_count.fetch_add(-1) == 1) {
delete ptr;
}
}
}
};
void push(stack<int>* s)
void
push(stack<int>* s)
{
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
}
}
void
pop(stack<int>* s)
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
}
void pop(stack<int>* s)
int
main()
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}
int main()
{
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}

View File

@@ -1,70 +1,70 @@
#include <thread>
#include <memory>
#include <atomic>
#include <iostream>
#include <memory>
#include <thread>
template <typename T>
class stack
{
private:
struct node
{
std::shared_ptr<T> data;
std::shared_ptr<node> next;
class stack {
private:
struct node {
std::shared_ptr<T> data;
std::shared_ptr<node> next;
node(T const& data_)
: data(std::make_shared<T>(data_))
{}
};
node(T const& data_) : data(std::make_shared<T>(data_)) {}
};
std::shared_ptr<node> head;
public:
void push(T const& data)
{
std::shared_ptr<node> const new_node = std::make_shared<node>(data);
new_node->next = std::atomic_load(head);
while (!std::atomic_compare_exchange_weak(&head,
&new_node->next, new_node));
}
std::shared_ptr<T> pop()
{
std::shared_ptr<node> old_head = std::atomic_load(head);
while (old_head && !std::atomic_compare_exchange_weak(&head,
&old_head, old_head->next));
return old_head ? old_head->data : std::shared_ptr<T>();
}
std::shared_ptr<node> head;
public:
void push(T const& data)
{
std::shared_ptr<node> const new_node = std::make_shared<node>(data);
new_node->next = std::atomic_load(head);
while (!std::atomic_compare_exchange_weak(&head, &new_node->next, new_node))
;
}
std::shared_ptr<T> pop()
{
std::shared_ptr<node> old_head = std::atomic_load(head);
while (old_head &&
!std::atomic_compare_exchange_weak(&head, &old_head, old_head->next))
;
return old_head ? old_head->data : std::shared_ptr<T>();
}
};
void push(stack<int>* s)
void
push(stack<int>* s)
{
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
}
}
void
pop(stack<int>* s)
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
}
void pop(stack<int>* s)
int
main()
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
std::shared_ptr<int> sp = std::make_shared<int>(1);
// printf("std::atomic_is_lock_free(std::shared_ptr): %d\n",
// std::atomic_is_lock_free(&sp));
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}
int main()
{
std::shared_ptr<int> sp = std::make_shared<int>(1);
//printf("std::atomic_is_lock_free(std::shared_ptr): %d\n",
// std::atomic_is_lock_free(&sp));
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}