#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; }