From fffe4528da8e10136372b7222805e159588f8bc9 Mon Sep 17 00:00:00 2001 From: Bo Wang Date: Tue, 1 Nov 2016 00:22:15 -0400 Subject: [PATCH] chapter 7 code --- ch7/Makefile | 34 ++++++ ch7/hazard_pointer.h | 63 ++++++++++++ ch7/queue_mpmc.cpp | 240 +++++++++++++++++++++++++++++++++++++++++++ ch7/queue_spsc.cpp | 102 ++++++++++++++++++ ch7/stack.cpp | 174 +++++++++++++++++++++++++++++++ ch7/stack_hp.cpp | 193 ++++++++++++++++++++++++++++++++++ ch7/stack_ref.cpp | 183 +++++++++++++++++++++++++++++++++ ch7/stack_sp.cpp | 70 +++++++++++++ 8 files changed, 1059 insertions(+) create mode 100644 ch7/Makefile create mode 100644 ch7/hazard_pointer.h create mode 100644 ch7/queue_mpmc.cpp create mode 100644 ch7/queue_spsc.cpp create mode 100644 ch7/stack.cpp create mode 100644 ch7/stack_hp.cpp create mode 100644 ch7/stack_ref.cpp create mode 100644 ch7/stack_sp.cpp diff --git a/ch7/Makefile b/ch7/Makefile new file mode 100644 index 0000000..f70669d --- /dev/null +++ b/ch7/Makefile @@ -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 + diff --git a/ch7/hazard_pointer.h b/ch7/hazard_pointer.h new file mode 100644 index 0000000..19c8f69 --- /dev/null +++ b/ch7/hazard_pointer.h @@ -0,0 +1,63 @@ +#include +#include +#include + +unsigned const max_hazard_pointers = 100; +struct hazard_pointer +{ + std::atomic id; + std::atomic 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& 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()); + } +}; + + + + diff --git a/ch7/queue_mpmc.cpp b/ch7/queue_mpmc.cpp new file mode 100644 index 0000000..a8fe100 --- /dev/null +++ b/ch7/queue_mpmc.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include + +// 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 +class queue +{ + private: + struct node; + struct counted_node_ptr + { + int external_count; + node* ptr; + }; + + std::atomic head; + std::atomic 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 data; + std::atomic count; + std::atomic 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& 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 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 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(); + } + + 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(res); + } + ptr->release_ref(); + } + } +}; + +void push(queue* q) +{ + for (int i = 0; i < 10; ++i) { + printf("pushing %d\n", i); + q->push(i); + } +} + +void pop(queue* q) +{ + int i = 0; + while (i < 10) { + std::shared_ptr p = q->pop(); + if (p) { + printf("poping %d\n", *p); + ++i; + } + } +} + +int main() +{ + queue q; + std::thread t1(push, &q); + std::thread t2(pop, &q); + t1.join(); + t2.join(); + return 0; +} + + diff --git a/ch7/queue_spsc.cpp b/ch7/queue_spsc.cpp new file mode 100644 index 0000000..f05c985 --- /dev/null +++ b/ch7/queue_spsc.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include + +template +class queue +{ + private: + struct node + { + std::shared_ptr data; + node* next; + + node() + : next(nullptr) + {} + }; + + std::atomic head; + std::atomic 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 pop() + { + node* old_head = pop_head(); + if (!old_head) { + return std::shared_ptr(); + } + + std::shared_ptr const res(old_head->data); + delete old_head; + return res; + } + + void push(T new_value) + { + std::shared_ptr new_data(std::make_shared(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* q) +{ + for (int i = 0; i < 10; ++i) { + printf("pushing %d\n", i); + q->push(i); + } +} + +void pop(queue* q) +{ + int i = 0; + while (i < 10) { + std::shared_ptr p = q->pop(); + if (p) { + printf("poping %d\n", *p); + ++i; + } + } +} + +int main() +{ + queue q; + std::thread t1(push, &q); + std::thread t2(pop, &q); + t1.join(); + t2.join(); + return 0; +} + diff --git a/ch7/stack.cpp b/ch7/stack.cpp new file mode 100644 index 0000000..21fbc8f --- /dev/null +++ b/ch7/stack.cpp @@ -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 +#include +#include +#include +#include + +template +class stack +{ + private: + struct node + { + std::shared_ptr data; + node *next; + + node(const T& data_) + : data(std::make_shared(data_)) + {} + }; + + std::atomic head; + std::atomic threads_in_pop; + std::atomic 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 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 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* s) +{ + for (int i = 0; i < 10; ++i) { + printf("pushing %d\n", i); + s->push(i); + } +} + +void pop(stack* s) +{ + int count = 0; + std::shared_ptr e; + while (count < 10) { + if (e = s->pop()) { + printf("popping %d\n", *e); + ++count; + } + } +} + +int main() +{ + stack s; + std::thread t1(push, &s); + std::thread t2(pop, &s); + t1.join(); + t2.join(); + return 0; +} + diff --git a/ch7/stack_hp.cpp b/ch7/stack_hp.cpp new file mode 100644 index 0000000..f25c4d1 --- /dev/null +++ b/ch7/stack_hp.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include "hazard_pointer.h" + +std::atomic& 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 +void do_delete(void* p) +{ + delete static_cast(p); +} + +struct data_to_reclaim +{ + void* data; + std::function deleter; + data_to_reclaim* next; + + template + data_to_reclaim(T* p) + : data(p) + , deleter(&do_delete) + , next(0) + {} + + ~data_to_reclaim() + { + deleter(data); + } +}; + +std::atomic 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 +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 +class stack +{ + private: + struct node + { + std::shared_ptr data; + node *next; + + node(const T& data_) + : data(std::make_shared(data_)) + {} + }; + + std::atomic 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 pop() + { + std::atomic& 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 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* s) +{ + printf("starting push\n"); + for (int i = 0; i < 10; ++i) { + printf("pushing %d\n", i); + s->push(i); + } +} + +void pop(stack* s) +{ + printf("starting pop\n"); + int count = 0; + std::shared_ptr e; + while (count < 10) { + if (e = s->pop()) { + printf("popping %d\n", *e); + ++count; + } + } +} + +int main() +{ + printf("creating stack\n"); + stack s; + printf("stack created\n"); + std::thread t1(push, &s); + std::thread t2(pop, &s); + t1.join(); + t2.join(); + return 0; +} + diff --git a/ch7/stack_ref.cpp b/ch7/stack_ref.cpp new file mode 100644 index 0000000..7d2c46d --- /dev/null +++ b/ch7/stack_ref.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include + +template +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 data; + std::atomic_int internal_count; + counted_node_ptr next; + + node(const T& data_) + : data(new T(data_)) + , internal_count(0) + {} + }; + + std::atomic head; + // NOTE: on those platforms that support a double-word-compare-and-swap + // operation, counted_node_ptr will be small enough for + // std::atomic 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 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(); + } + + // 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 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* s) +{ + for (int i = 0; i < 10; ++i) { + printf("pushing %d\n", i); + s->push(i); + } +} + +void pop(stack* s) +{ + int count = 0; + std::shared_ptr e; + while (count < 10) { + if (e = s->pop()) { + printf("popping %d\n", *e); + ++count; + } + } +} + +int main() +{ + stack s; + std::thread t1(push, &s); + std::thread t2(pop, &s); + t1.join(); + t2.join(); + return 0; +} + + diff --git a/ch7/stack_sp.cpp b/ch7/stack_sp.cpp new file mode 100644 index 0000000..10f3acc --- /dev/null +++ b/ch7/stack_sp.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include + +template +class stack +{ + private: + struct node + { + std::shared_ptr data; + std::shared_ptr next; + + node(T const& data_) + : data(std::make_shared(data_)) + {} + }; + + std::shared_ptr head; + public: + void push(T const& data) + { + std::shared_ptr const new_node = std::make_shared(data); + new_node->next = std::atomic_load(head); + while (!std::atomic_compare_exchange_weak(&head, + &new_node->next, new_node)); + } + std::shared_ptr pop() + { + std::shared_ptr 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(); + } +}; + +void push(stack* s) +{ + for (int i = 0; i < 10; ++i) { + printf("pushing %d\n", i); + s->push(i); + } +} + +void pop(stack* s) +{ + int count = 0; + std::shared_ptr e; + while (count < 10) { + if (e = s->pop()) { + printf("popping %d\n", *e); + ++count; + } + } +} + +int main() +{ + std::shared_ptr sp = std::make_shared(1); + //printf("std::atomic_is_lock_free(std::shared_ptr): %d\n", + // std::atomic_is_lock_free(&sp)); + stack s; + std::thread t1(push, &s); + std::thread t2(pop, &s); + t1.join(); + t2.join(); + return 0; +} +