Thread-Safe Stack Implementation

C++
Author

Quasar

Published

February 23, 2025

Basic thread-safe stack and queue

We can use C++ synchronization primitives to implement a basic thread-safe stack and queue.

Thread-safe stack

Implementation Notes

#include <iostream>
#include <stack>
#include <algorithm>
#include <thread>
#include <mutex>
#include <memory>
#include <type_traits>
#include <concepts>
#include <shared_mutex>

namespace dev {
   
    template<typename T>
    
    class threadsafe_stack {
    public:

        using Mutex = std::shared_mutex;

        /* Constructors */
        
        // Default constructor
        threadsafe_stack() {}

        /*
        * This stack is copy-constructible. The copy constructor locks the mutex in the 
        * source object and then copies the internal stack.
        */
        threadsafe_stack(const threadsafe_stack& other) {
            std::unique_lock<Mutex> lck(other.mtx);
            m_stack = other.m_stack;
        }

        // Copy assignment
        threadsafe_stack& operator=(const threadsafe_stack& other) = delete;

        ~threadsafe_stack() = default;

        /*
        * pop() is a wrapper over std::stack<T>::top() and std::stack<T>::pop().
        * This interface avoids any race conditions that occur between calls to top()
        * and pop().
        */
        std::shared_ptr<T> pop()
        {
            std::unique_lock<Mutex> lck(mtx);
            if (m_stack.empty())
                throw std::exception("Exception - pop() invoked on an empty stack!");

            auto result = std::make_shared<T>(std::move(m_stack.top()));
            m_stack.pop();
            return result;
        }

        void pop(T& result)
        {
            std::unique_lock<Mutex> lck(mtx);
            if (m_stack.empty())
                throw std::exception("Exception - pop() invoked on an empty stack");

            result = std::move(m_stack.top());
            m_stack.pop();
        }

        void push(T value)
        {
            std::unique_lock<Mutex> lck(mtx);
            m_stack.push(std::move(value));
        }

        int size() {
            std::shared_lock<Mutex> lck(mtx);
            return m_stack.size();
        }

        /* 
        * Acquire a std::shared_lock<Mtx> on the mutex, so multiple
        * readers can read the stack.
        */
        bool empty() {
            std::shared_lock<Mutex> lck(mtx);
            return m_stack.empty();
        }

    private:
        std::stack<T> m_stack;
        Mutex mtx;
    };
}

int main()
{
    dev::threadsafe_stack<int> stck;
    
    std::thread writer1(
        [&stck]() {
            for (int i{ 1 };i <= 1000;++i) {
                stck.push(i);
            }
        }
    );

    std::thread writer2(
        [&stck]() {
            for (int i{ 1001 };i <= 2000;++i) {
                stck.push(i);
            }
        }
    );

    writer1.join();
    writer2.join();

    std::cout << "\n" << "Stack size = " << stck.size();
}