A Lock-Free Stack: A Hazard Pointer Implementation
Hazard Pointers solve all issues of the previous implementation: A Lock-Free Stack: A Simple Garbage Collector.

From one Week to two Weeks
You may have already noticed. I will change my blog publishing frequency from one to two weeks in the future. Writing a post using your voice is exceptionally exhausting and time-consuming.
Hazard Pointers
The term hazard pointers goes back to Maged Michael. Hazard pointers solve the classical problem of lock-free data structures such as a lock-free stack: When can a thread safely delete a node of a data structure while other threads can use this node at the same time?
Although a hazard pointer provides a general solution for the common problem of safe memory
reclamation in lock-free data structures, I want to present it from the perspective of our lock-free
stack.

A hazard pointer is a single-writer, multi-reader pointer. All hazard pointers build a linked list and are initialized with a null pointer. When a thread uses a stack node, it puts the node’s address into a hazard pointer, indicating that it uses this node and is the exclusive owner of the used hazard pointer. When the thread is done using the node, it sets the hazard pointer to a null pointer and, therefore, releases its ownership. A thread keeps a list of hazard pointers standing for the nodes the thread is using and can not be deleted. When a thread wants to delete a node, it traverses the list of all hazard pointers and checks if the node is used. If the node is not in use, it deletes it. If the node is in use, it will eventually be put on a retire list of the to-be-deleted nodes. Eventually, the node is only added to the retire list if it is not yet on the list.
This is the case for our lock-free stack. The member function topAndPop has two jobs regarding memory reclamation. First, it manages the to-be-deleted node; second, it traverses the retire list of nodes and deletes them if they aren’t used anymore.
I need the following member function in a new implementation of topAndPop
based on the previous description: getHazardPointer
to get a reference to a hazard pointer, retireList.addNode
, and retireList.deleteUnusedNodes
to add a node to the retire list retireList
. Additionally, retireList.deleteUnusedNodes
to delete all nodes from the retire list that are no longer used. Additionally, the member function retireList.deleteUnusedNode
uses the helper function retireList.isInUse
to decide if a node is currently used. The member function isInUse
is also handy in topAndPop
decide whether the current node should be added to the retire list or directly deleted.
What does this mean for my previous LockFreeStack
implementation (
A Lock-Free Stack: A Complete Implementation) without memory reclamation? Let’s see. The following program shows the lock-free stack implementation based on hazard pointers.
A Hazard Pointer Implementation
// lockFreeStackHazardPointers.cpp #include <atomic> #include <cstddef> #include <future> #include <iostream> #include <stdexcept> #include <thread> template <typename T> concept Node = requires(T a) { {T::data}; { *a.next } -> std::same_as<T&>; }; template <typename T> struct MyNode { T data; MyNode* next; MyNode(T d): data(d), next(nullptr){ } }; constexpr std::size_t MaxHazardPointers = 50; template <typename T, Node MyNode = MyNode<T>> struct HazardPointer { std::atomic<std::thread::id> id; std::atomic<MyNode*> pointer; }; template <typename T> HazardPointer<T> HazardPointers[MaxHazardPointers]; template <typename T, Node MyNode = MyNode<T>> class HazardPointerOwner { HazardPointer<T>* hazardPointer; public: HazardPointerOwner(HazardPointerOwner const &) = delete; HazardPointerOwner operator=(HazardPointerOwner const &) = delete; HazardPointerOwner() : hazardPointer(nullptr) { for (std::size_t i = 0; i < MaxHazardPointers; ++i) { std::thread::id old_id; if (HazardPointers<T>[i].id.compare_exchange_strong( old_id, std::this_thread::get_id())) { hazardPointer = &HazardPointers<T>[i]; break; } } if (!hazardPointer) { throw std::out_of_range("No hazard pointers available!"); } } std::atomic<MyNode*>& getPointer() { return hazardPointer->pointer; } ~HazardPointerOwner() { hazardPointer->pointer.store(nullptr); hazardPointer->id.store(std::thread::id()); } }; template <typename T, Node MyNode = MyNode<T>> std::atomic<MyNode*>& getHazardPointer() { thread_local static HazardPointerOwner<T> hazard; return hazard.getPointer(); } template <typename T, Node MyNode = MyNode<T>> class RetireList { struct RetiredNode { MyNode* node; RetiredNode* next; RetiredNode(MyNode* p) : node(p), next(nullptr) { } ~RetiredNode() { delete node; } }; std::atomic<RetiredNode*> RetiredNodes; void addToRetiredNodes(RetiredNode* retiredNode) { retiredNode->next = RetiredNodes.load(); while (!RetiredNodes.compare_exchange_strong(retiredNode->next, retiredNode)); } public: bool isInUse(MyNode* node) { for (std::size_t i = 0; i < MaxHazardPointers; ++i) { if (HazardPointers<T>[i].pointer.load() == node) return true; } return false; } void addNode(MyNode* node) { addToRetiredNodes(new RetiredNode(node)); } void deleteUnusedNodes() { RetiredNode* current = RetiredNodes.exchange(nullptr); while (current) { RetiredNode* const next = current->next; if (!isInUse(current->node)) delete current; else addToRetiredNodes(current); current = next; } } }; template<typename T, Node MyNode = MyNode<T>> class LockFreeStack { std::atomic<MyNode*> head; RetireList<T> retireList; public: LockFreeStack() = default; LockFreeStack(const LockFreeStack&) = delete; LockFreeStack& operator= (const LockFreeStack&) = delete; void push(T val) { MyNode* const newMyNode = new MyNode(val); newMyNode->next = head.load(); while( !head.compare_exchange_strong(newMyNode->next, newMyNode) ); } T topAndPop() { std::atomic<MyNode*>& hazardPointer = getHazardPointer<T>(); MyNode* oldHead = head.load(); do { MyNode* tempMyNode; do { tempMyNode = oldHead; hazardPointer.store(oldHead); oldHead = head.load(); } while( oldHead != tempMyNode ); } while( oldHead && !head.compare_exchange_strong(oldHead, oldHead->next) ) ; if ( !oldHead ) throw std::out_of_range("The stack is empty!"); hazardPointer.store(nullptr); auto res = oldHead->data; if ( retireList.isInUse(oldHead) ) retireList.addNode(oldHead); else delete oldHead; retireList.deleteUnusedNodes(); return res; } }; int main(){ LockFreeStack<int> lockFreeStack; auto fut = std::async([&lockFreeStack]{ lockFreeStack.push(2011); }); auto fut1 = std::async([&lockFreeStack]{ lockFreeStack.push(2014); }); auto fut2 = std::async([&lockFreeStack]{ lockFreeStack.push(2017); }); auto fut3 = std::async([&lockFreeStack]{ return lockFreeStack.topAndPop(); }); auto fut4 = std::async([&lockFreeStack]{ return lockFreeStack.topAndPop(); }); auto fut5 = std::async([&lockFreeStack]{ return lockFreeStack.topAndPop(); }); fut.get(), fut1.get(), fut2.get(); std::cout << fut3.get() << '\n'; std::cout << fut4.get() << '\n'; std::cout << fut5.get() << '\n'; }
The program runs as expected.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.

What’s Next?
I will analyse the program step by step.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, Honey Sukesan, bruce_lee_wayne, Silviu Ardelean, and Seeker.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | ![]() |
My special thanks to PVS-Studio | ![]() |
My special thanks to Tipi.build | ![]() |
My special thanks to Take Up Code | ![]() |
My special thanks to SHAVEDYAKS | ![]() |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org