In my previous post I presented the implementation of dangerous pointers: a lock-free stack that has a dangerous-pointer implementation. Today I will explain the implementation.MyNode
There is a square template that is parameter by data type: which includes: data
, MyNode
Model the concept Node
,
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 ++.
template
concept Node = requires(T a) {
{T::data};
{ *a.next } -> std::same_as;
};
template
struct MyNode {
T data;
MyNode* next;
MyNode(T d): data(d), next(nullptr){ }
};
Concepts are predicted during compilation time. They define semantic restrictions for template parameters. concept Node
Is necessary data
-Member and a indicator next
One Node
Return.
(Image: Rainer Grim)
The data types of lockfreestackhazardpointers.cPP program are essentially on it data
-Member and Concept Node
Parameter. MyNode
Model the concept Node
Here, for example, announcement LockFreeStack
,
template>
class LockFreeStack;
This is what the program looks with a lock-free stack:
Template>
class LockFreeStack {
std::atomic head;
RetireList 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& hazardPointer = getHazardPointer();
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;
}
};
Call push
It is not important from an approach, there, there head
A atomic step is updated. Also, the call guarantees compare_exchange_strong
He head
Always the current head of the stack.
Due to dangerous points, call will be called topAndPop
More complex. First, the function refers to getHazardPointer
On the dangerous indicator for current thread. Call hazardPointer.store(oldHead)
Current threads owner of danger indicator, and calls hazardPointer.store(nullptr)
Leaves its possession.
First I analyze the internal and external do-white loops. The inner loop holds a dangerous indicator on the stack’s head. When the following is implemented, the Do-Whil Loop ends: oldHead == tempNode
,
Both are the same miles the same oldHead
The current is still the current of the lock-free stack. oldHead
Was set and now the current head cannot be, because a separate thread can come in and oldHead
Already managed.
The outer do-whong is similar to the previous implementation of the lock-free stack. I am in the loop with me compare_exchange_strong
And put on the head oldHead->next
Finally Head
Stack head. Memberfunktion topAndPop
The head value should be returned and removed. Before me oldHead
Use, I have to check what oldHead
There is no zero indicator. If this is the case, I throw an exception.
Rest of topAndPop
Is simple. Call retireList.isInUse(oldHead)
Checked what oldHead
Is still used. This depends on the result of the review oldHead
Selection list retireList.addNode
It is added if it is not yet in the list or has been removed. Final call retireList.deleteUnusedNodes
Member function is the most labor -confused call topAndPop
Member ceremony retireListe.deleteUnusedNodes
Goes through the whole Retire
-List and removes all nodes that are no longer used.
For the reasons for the performance, the call should be made retireList.deleteUnusedNodes
Every time you don’t call topAndPop
This is a better strategy, member function deleteUnusedNodes
Call when you have length Retire
-List exceeds a certain range. Length of agar Retire
-List, for example, doubled to double the stack, at least half of the knots can be removed. This is a compromise between requirements for limit performance and storage consumption.
Work here getHazardPointer
,
template >
sttd::atomic& getHazardPointer() {
thread_local static HazardPointerOwner hazard;
return hazard.getPointer();
}
The function refers to a dangerous indicator using the owner hazard
A thread restaurant and stable variable. Therefore, each thread receives his copy of the threat indicator owner and his lifetime is bound for the lifetime of his own thread. The bound lifetime of the owner of the Hazard pointer is of significant importance, as it is guaranteed to be removed by dangerous pointers if the thread restaurant owners of the dangerous pointer are destroyed. I write more about this Raii object in my analysis of data types HazardPointerOwner
,
What will happen next?
In my next article I will explain the remaining implementation.
(RME)
