Home DEVELOPER C ++ 26: Complete Implementation of Lock-Free Stack

C ++ 26: Complete Implementation of Lock-Free Stack

0


According to the principle for lock-free programming in the previous article, the full implementation of the lock-free stack has now been followed.

Advertisement





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 ++.

ENTERJS 2025: five workshops for angular, safety, reaction and more



This paragraph is optional. In my examples I use standard memory regulations: Sequential stabilityThe reason for this is simple. The sequential stability provides the strongest guarantee of all memory rules and is therefore easier to use compared to other storage rules. The sequential stability is an ideal starting point for the design of lock-free data structures. In further adaptation stages, memory rules can be weakened and A Received release semantha Or Comfortable semantics used.

Depending on the architecture, it may be that a weakness of the memory system does not pay. For example, the X86 memory model is one of the strongest memory models of all more modern architecture. Therefore, the cancellation of sequential stability and the use of a weak memory order cannot improve desired performance. In contrast, Armv8, Powerpc, Itanium and especially Dec Alpha can pay if the sequential stability is canceled.

The simplified stack version of the previous article has two problems: first, it does not have a bridge operation and second, it does not release any memory.

A stack member usually supports the functions push, pop And topImplementation of members works pop And top In a threadproof manner, it does not guarantee from the call topAfter popthread safe. It may happen that a thread t1 stack.top() Call and other threads t2 Overlade, stack.top() And then stack.pop() Call. Last call now pop On the wrong stack size.

As a result, two elements functions are in the following implementation top And pop Combine in the same function: topAndPop,

// lockFreeStackWithLeaks.cpp

#include 
#include 
#include 
#include 

template
class LockFreeStack {
 private:
    struct Node {
        T data;
        Node* next;
        Node(T d): data(d), next(nullptr){ }
    };
    std::atomic head;
 public:
    LockFreeStack() = default;
    LockFreeStack(const LockFreeStack&) = delete;
    LockFreeStack& operator= (const LockFreeStack&) = delete;
   
    void push(T val) {
        Node* const newNode = new Node(val);
        newNode->next = head.load();
        while( !head.compare_exchange_strong(newNode->next, newNode) );
    }

    T topAndPop() {
        Node* oldHead = head.load();                                                 // 1
        while( oldHead && !head.compare_exchange_strong(oldHead, oldHead->next) ) {  // 2
            if ( !oldHead ) throw std::out_of_range("The stack is empty!");          // 3
        }
        return oldHead->data;                                                        // 4
    }
};
   
int main(){

    LockFreeStack 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();                            // 5  
    
    std::cout << fut3.get() << '\n';
    std::cout << fut4.get() << '\n';
    std::cout << fut5.get() << '\n';

}

Member ceremony topAndPop Gives back the top element of the stack. She reads the head element of the stack (line 1) and creates a new head to the next knot oldHead No nullptr Is (line 2). oldhead One is nullptrWhen the stack is empty. I throw an exception when the stack is empty (line 3). A special non-value refund or return of A std::optional There is also a valid option. Popping value causes a disadvantage: if there is an exception like a copy constructor std::bad_alloc The trigger, the value is lost. Finally, the member returns the function head element (line 4).

Call fut.get(), fut1.get(), fut2.get() (Row 5) Make sure that the related promise is made. If you do not give the launch policy, the promise can delay the collar thread. Lazy means that the promise is made only when the future get Or wait Asks for its result. Promise can also begin in a different thread:

auto fut = std::async(std::launch::asnyc, (&conStack){ conStack.push(2011); });
auto fut1 = std::async(std::launch::asnyc, (&conStack){ conStack.push(2014); });
auto fut2 = std::async(std::launch::asnyc, (&conStack){ conStack.push(2017); });

The output of the program provides at the end:



Although the lock-free stack launched push And topAndPop Supported, he has a serious problem: he loses memory. Why you can oldHead Not just after calling head.compare_exchange_strong(oldHead, oldHead->next) (2) In member ceremony topAndPop be removed? The answer is another thread oldHead Can use. We analyze the functions of the members push And topAndPopExtracts of simultaneous push No problem because call !head.compare_exchange_strong(newNode->next, newNode) newNode->next The atom was updated for new heads. This also applies if only one call topAndPop takes place. The problem occurs when many thoughts topAndPop With or without call push Are nested. Is being removed oldHeadWhile another thread uses it, it will be frightening because extinguishing oldHead Before or after its update should always be done on new heads: oldHead->next (2).

Thanks to RCU and Hazard Points, I can remove memory loss in my next article.


(RME)

Product Worker: Product owner has to know that ten ways

NO COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Exit mobile version