chapter 7 code
This commit is contained in:
34
ch7/Makefile
Normal file
34
ch7/Makefile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
CPP = g++-4.9
|
||||||
|
CPPFLAGS = -g -std=c++0x
|
||||||
|
OFLAG = -o
|
||||||
|
.SUFFIXES : .o .cpp .c
|
||||||
|
.cpp.o :
|
||||||
|
$(CPP) $(CPPFLAGS) -c $<
|
||||||
|
.c.o :
|
||||||
|
$(CPP) $(CPPFLAGS) -c $<
|
||||||
|
|
||||||
|
PROGS = stack stack_hp queue_spsc queue_mpmc
|
||||||
|
|
||||||
|
all: $(PROGS)
|
||||||
|
|
||||||
|
stack: stack.o
|
||||||
|
$(CPP) $(OFLAG) stack stack.o -lpthread
|
||||||
|
|
||||||
|
stack_hp: stack_hp.o
|
||||||
|
$(CPP) $(OFLAG) stack_hp stack_hp.o -lpthread
|
||||||
|
|
||||||
|
stack_sp: stack_sp.o
|
||||||
|
$(CPP) $(OFLAG) stack_sp stack_sp.o -lpthread
|
||||||
|
|
||||||
|
stack_ref: stack_ref.o
|
||||||
|
$(CPP) $(OFLAG) stack_ref stack_ref.o -lpthread
|
||||||
|
|
||||||
|
queue_spsc: queue_spsc.o
|
||||||
|
$(CPP) $(OFLAG) queue_spsc queue_spsc.o -lpthread
|
||||||
|
|
||||||
|
queue_mpmc: queue_mpmc.o
|
||||||
|
$(CPP) $(OFLAG) queue_mpmc queue_mpmc.o -lpthread
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f ${PROGS} *.o
|
||||||
|
|
||||||
63
ch7/hazard_pointer.h
Normal file
63
ch7/hazard_pointer.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
|
hazard_pointer hazard_pointers[max_hazard_pointers];
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
240
ch7/queue_mpmc.cpp
Normal file
240
ch7/queue_mpmc.cpp
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
// NOTE: if atomic operations on std::shared_ptr<> are lock-free,
|
||||||
|
// then compare/exchange would be enough to implement the lock-free
|
||||||
|
// mpmc queue
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
{}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
queue<int> q;
|
||||||
|
std::thread t1(push, &q);
|
||||||
|
std::thread t2(pop, &q);
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
102
ch7/queue_spsc.cpp
Normal file
102
ch7/queue_spsc.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class queue
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
struct node
|
||||||
|
{
|
||||||
|
std::shared_ptr<T> data;
|
||||||
|
node* next;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
queue()
|
||||||
|
: head(new node)
|
||||||
|
, tail(head.load())
|
||||||
|
{}
|
||||||
|
|
||||||
|
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> 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(queue<int>* q)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
queue<int> q;
|
||||||
|
std::thread t1(push, &q);
|
||||||
|
std::thread t2(pop, &q);
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
174
ch7/stack.cpp
Normal file
174
ch7/stack.cpp
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
// Listing 7.4
|
||||||
|
|
||||||
|
// The reference-counted reclamation machinery
|
||||||
|
// This works reasonably well in low-load situations, where there are sutiable
|
||||||
|
// quiescent points at which no threads are in pop(). In high-load situaions,
|
||||||
|
// there may never be such a quiescent state. Under such a scenario, the
|
||||||
|
// 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
|
||||||
|
// 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 <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
stack<int> s;
|
||||||
|
std::thread t1(push, &s);
|
||||||
|
std::thread t2(pop, &s);
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
193
ch7/stack_hp.cpp
Normal file
193
ch7/stack_hp.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include "hazard_pointer.h"
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~data_to_reclaim()
|
||||||
|
{
|
||||||
|
deleter(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<data_to_reclaim*> nodes_to_reclaim;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
183
ch7/stack_ref.cpp
Normal file
183
ch7/stack_ref.cpp
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <atomic>
|
||||||
|
#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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node
|
||||||
|
{
|
||||||
|
std::shared_ptr<T> data;
|
||||||
|
std::atomic_int internal_count;
|
||||||
|
counted_node_ptr next;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
stack<int> s;
|
||||||
|
std::thread t1(push, &s);
|
||||||
|
std::thread t2(pop, &s);
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
70
ch7/stack_sp.cpp
Normal file
70
ch7/stack_sp.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#include <thread>
|
||||||
|
#include <memory>
|
||||||
|
#include <atomic>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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_))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user