Implement a thread-safe queue

A thread-safe queue is a data structure designed to be used concurrently by multiple threads without data corruption or race conditions. Ensuring thread safety is crucial in multithreaded applications where multiple threads may attempt to access or modify the queue simultaneously. A thread-safe queue typically employs synchronization mechanisms to provide safe and ordered access to its elements.

#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>

template <typename T>
class ThreadSafeQueue {
private:
    std::queue<T> queue_;
    mutable std::mutex mutex_;
    std::condition_variable condition_;

public:
    void enqueue(const T& item) {
        std::lock_guard<std::mutex> lock(mutex_);
        queue_.push(item);
        condition_.notify_one();  // Notify a waiting consumer
    }

    T dequeue() {
        std::unique_lock<std::mutex> lock(mutex_);
        condition_.wait(lock, [this] { return !queue_.empty(); });
        
        T item = queue_.front();
        queue_.pop();
        return item;
    }
};

void producer(ThreadSafeQueue<int>& q, int id) {
    for (int i = 0; i < 5; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        q.enqueue(id * 100 + i);
    }
}

void consumer(ThreadSafeQueue<int>& q, int id) {
    for (int i = 0; i < 5; ++i) {
        int item = q.dequeue();
        std::cout << "Consumer " << id << " dequeued: " << item << std::endl;
    }
}

int main() {
    ThreadSafeQueue<int> tsQueue;

    std::thread producer1(producer, std::ref(tsQueue), 1);
    std::thread producer2(producer, std::ref(tsQueue), 2);
    std::thread consumer1(consumer, std::ref(tsQueue), 1);
    std::thread consumer2(consumer, std::ref(tsQueue), 2);

    producer1.join();
    producer2.join();
    consumer1.join();
    consumer2.join();

    return 0;
}

Output:

Consumer 1 dequeued: Consumer 1002 dequeued: 200

Consumer Consumer 2 dequeued: 1011 dequeued: 
201
Consumer 2 dequeued: 102
Consumer 2 dequeued: 202
Consumer 2 dequeued: 103
Consumer 1 dequeued: 203
Consumer 1 dequeued: 104
Consumer 1 dequeued: 204

In this example:

  • The ThreadSafeQueue class has methods enqueue to add an item to the queue and dequeue to retrieve an item from the queue.
  • std::mutex is used to guard access to the shared queue, ensuring that only one thread can modify the queue at a time.
  • std::condition_variable is used to coordinate between the producer and consumer threads. The consumer threads wait for the queue to become non-empty before attempting to dequeue.
  • The producer function adds items to the queue, and the consumer function retrieves items from the queue.