In my previous article I started explaining the Hazard Points, which I continue in this post.
Rainer Grim has been working as a software architect, team and training manager for many years. He likes to write articles on programming languages ​​C ++, Python and Haskel, but also likes to speak at expert conferences. On his blog modern C ++, he deal intensively with his passion C ++.
Retired list
RetireList
Public members are tasks isInUse
, addNode
And deleteUnusedNodes
Also, it has internal class RetireNode
IT’s nuclear member and private member function addToRetiredNodes
,
template >
class RetireList {
struct RetiredNode {
MyNode* node;
RetiredNode* next;
RetiredNode(MyNode* p) : node(p), next(nullptr) { }
~RetiredNode() {
delete node;
}
};
std::atomic 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(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;
}
}
};

Let’s start with data type interface RetireList
Member ceremony isInUse
Check whether node
is in use. This variable goes through the template HazardPointers
It is parameter on the data type of the node. HazardPointers
Is a se-desert HazardPointer
Length 50. One HazardPointer
There are a atomic thread ID and a nuclear indicator on a knot:
constexpr std::size_t MaxHazardPointers = 50;
template >
struct HazardPointer {
std::atomic<:thread::id> id;
std::atomic pointer;
};
template
HazardPointer HazardPointers(MaxHazardPointers);
Such as the use of an STL container std::set
As HazardPointers
It is very practical. std::set
Already arranged and on average a continuous access guarantees time, but there is a major problem: it is not thread-safe.
Member ceremony addNode
Takes a knot, a private member calls the function addToRetiredNodes
And add knots to one RetiredNode
One. RetiredNode
There is a Raii object and guarantees that the attached knot will be destroyed, which leaves its memory. All retired nodes make a bus linked list.
Member ceremony deleteUnusedNodes
The bus of retired knot goes through the following pattern in the linked list:
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;
}
}
It checks the current knot, refers to the next knot current->next
And updates the current knot with the next knot. Finally, the current knot is destroyed if it is no longer used or added to harmful nodes. Private member function addToRetireNodes
Add only decayed knots to the linked list. To complete her task, she loads the dated nodes and creates a new knot retiredNode
For the new head of the simple linked list.
First retiredNode
The simple linked list becomes the new head, I have to make sure that it is still the head of the simple linked list, as another thread may come and change the head of the simple linked list. While thanks for the loop retiredNode
Only when on the new head retiredNode->next = RetiredNodes.load()
come into force. If not, it becomes retiredNode->next
But RetiredNodes.load()
Updated.
Only one part of the puzzle is missing: the owner of the dangerous indicator:
template >
class HazardPointerOwner {
HazardPointer* 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(i).id.compare_exchange_strong(
old_id, std::this_thread::get_id())) {
hazardPointer = &HazardPointers(i);
break;
}
}
if (!hazardPointer) {
throw std::out_of_range(„No hazard pointers available!“);
}
}
std::atomic& getPointer() {
return hazardPointer->pointer;
}
~HazardPointerOwner() {
hazardPointer->pointer.store(nullptr);
hazardPointer->id.store(std::thread::id());
}
};
HazardPointerOwner
Catchs one HazardPointer
It is in the constitution through everyone HazardPointer
set. Call compare_exchange_strong
Check in an atomic step whether the current has been passed HazardPointer
Not set, and now depends on the ID of the thread made (std::this_thread::get_id()
In success HazardPointer
To new HazardPointer
It is returned to the customer that the member works getPointer
Call. When all indicate the danger HazardPointers
Constructor solves std::out_of_range Exception
Outside. Finally HazardPointerOwner
hazardPointer
Back to your standard state.
What will happen next?
In C ++ 26 we will get a danger indicator. I will write about it in my next article.
(RME)
