Run clang-format
This commit is contained in:
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
267
ch7/stack.cpp
267
ch7/stack.cpp
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
306
ch7/stack_hp.cpp
306
ch7/stack_hp.cpp
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
112
ch7/stack_sp.cpp
112
ch7/stack_sp.cpp
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user