Lesson 1
Thread-safe Stack using Locks
Thread-safe Stack using Locks

Welcome to the first lesson in our course on lock-based concurrent data structures. Here, we will delve into implementing a thread-safe stack using locks in C++. You may remember that we've already touched upon the topic of synchronization mechanisms like std::mutex in past discussions. This lesson provides a hands-on approach to exploring how locks can help us ensure thread safety and consistency when accessing shared resources. Let’s embark on this journey where you'll transform typical data structures into robust, concurrent ones.

What You'll Learn

In this lesson, you will learn how to create a stack that multiple threads can access without encountering race conditions. A thread-safe stack ensures that operations like pushing and popping elements can be performed safely across multiple threads. You'll employ std::mutex for synchronization, which is crucial for locking access to the stack and managing concurrent tasks effectively.

Here’s a quick look at what the core of our thread-safe stack will include:

C++
1class ThreadsafeStack { 2private: 3 std::stack<int> data; 4 mutable std::mutex m; 5 6public: 7 void push(int new_value) { 8 std::lock_guard<std::mutex> lock(m); 9 data.push(std::move(new_value)); 10 } 11 12 void pop(int& value) 13 { 14 std::lock_guard<std::mutex> lock(m); 15 if (data.empty()) { 16 throw std::runtime_error("Stack is empty!"); 17 } 18 value = std::move(data.top()); 19 data.pop(); 20 } 21 22 bool empty() const { 23 std::lock_guard<std::mutex> lock(m); 24 return data.empty(); 25 } 26};

Let's break down both the methods:

  • push(int new_value): This method pushes a new element onto the stack. It uses a std::lock_guard to lock the mutex m and ensure that only one thread can access the stack at a time. Notice that we use std::move to transfer the value to the stack, which is more efficient than copying.
  • pop(int& value): This method pops the top element from the stack and stores it in the value reference. It also uses a std::lock_guard to lock the mutex m and prevent multiple threads from accessing the stack simultaneously. If the stack is empty, it throws a std::runtime_error. Again, we use std::move to transfer the value from the stack. Note, that the pop method of standard std::stack does not take any arguments, however, we have modified it to take a reference to an int to store the popped value for demonstration purposes.
  • empty() const: This method checks if the stack is empty. It also uses a std::lock_guard to lock the mutex m and ensure that the operation is thread-safe.

We can use this stack in a multi-threaded environment without worrying about data corruption. Here is a sample usage with 10 threads pushing and 10 popping elements:

C++
1int main() { 2 ThreadsafeStack stack; 3 std::vector<std::thread> threads; 4 5 for (int i = 0; i < 10; ++i) { 6 threads.emplace_back([&stack, i] { 7 stack.push(i); 8 std::cout << "Pushed: " << i << std::endl; 9 }); 10 } 11 12 for (int i = 0; i < 10; ++i) { 13 threads.emplace_back([&stack] { 14 int value; 15 stack.pop(value); 16 std::cout << "Popped: " << value << std::endl; 17 }); 18 } 19 20 for (auto& t : threads) { 21 t.join(); 22 } 23 24 return 0; 25}

In this lesson, you will learn how to implement a thread-safe stack using locks and understand the underlying concepts that make it work.

Why It Matters

Understanding how to implement a thread-safe stack is fundamental in building applications that require concurrent processing. Whether dealing with real-time data processing or any multi-threaded environment, ensuring safe access to shared data structures is imperative. By implementing locks, you minimize errors like race conditions and ensure the integrity of your data even when multiple threads are at play.

The knowledge you gain here not only enables you to handle concurrency in stacks but also sets a strong foundation for managing other data structures. It's exciting to see how these concepts come together to enhance application performance and reliability. Are you ready to take the next step and apply these concepts in practice? Let's dive in!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.