Skip to content

Instantly share code, notes, and snippets.

@CaglayanDokme
Created September 16, 2023 09:13
Show Gist options
  • Save CaglayanDokme/2fd4278969cf9683c5e5ad0ca5534020 to your computer and use it in GitHub Desktop.
Save CaglayanDokme/2fd4278969cf9683c5e5ad0ca5534020 to your computer and use it in GitHub Desktop.
Thread Safe Template Message Queue in C++17

Implementing a thread-safe message queue is crucial in concurrent and multi-threaded programming to facilitate safe communication between threads. A thread-safe message queue ensures that multiple threads can share data without running into issues such as data races, deadlocks, or data corruption. By using synchronization mechanisms like mutexes and condition variables, a thread-safe message queue provides a structured and controlled environment for threads to exchange information. Additionally, designing the message queue as a template, as seen in the provided MsgQueue class, adds a layer of flexibility and adaptability. It allows developers to use the same message queue implementation for a wide range of data types, making it a versatile tool in various applications. This template-based approach promotes code reuse, reducing the need to create specialized message queue classes for different data types and enhancing the overall efficiency and maintainability of concurrent software systems.

#pragma  once

#include <queue>
#include <mutex>
#include <condition_variable>

template<class ElementType>
class MsgQueue {
public: /** Construction **/
    MsgQueue() = default;

    // Forbid copying and moving
    MsgQueue(const MsgQueue &) = delete;
    MsgQueue(MsgQueue&&) = delete;
    MsgQueue operator=(const MsgQueue &) = delete;
    MsgQueue& operator=(MsgQueue&&) = delete;

public: /** Methods **/
    void push(const ElementType &element)
    {
        {
            std::lock_guard lock(m_lock);
            m_messages.push(element);
        }

        m_notifier.notify_one();
    }

    void push(ElementType &&element)
    {
        {
            std::lock_guard lock(m_lock);
            m_messages.push(std::move(element));
        }

        m_notifier.notify_one();
    }

    template<class... Args>
    void emplace(Args&&... args)
    {
        {
            std::lock_guard lock(m_lock);
            m_messages.emplace(std::forward<Args>(args)...);
        }

        m_notifier.notify_one();
    }

    [[nodiscard]] ElementType &front()
    {
        std::unique_lock lock(m_lock);

        if(m_messages.empty()) {
            m_notifier.wait(lock,  [&]{ return !m_messages.empty(); });
        }

        return m_messages.front();
    }

    void pop()
    {
        if(!m_messages.empty()) {
            std::lock_guard lock(m_lock);
            m_messages.pop();
        }
    }

    [[nodiscard]] bool empty() const
    {
        return m_messages.empty();
    }

    [[nodiscard]] size_t size() const
    {
        return m_messages.size();
    }

private: /** Members **/
    std::queue<ElementType> m_messages;
    std::condition_variable m_notifier;
    std::mutex m_lock;
};

Example usage:

#pragma once

#include <thread>
#include <variant>
#include <string>
#include <iostream>
#include "MsgQueue.hpp"

class ConsumerTask {
public:
    ConsumerTask() {
        task = std::thread(&ConsumerTask::run, this);
    }

public: /** Methods **/
    [[noreturn]] void run()
    {
        while(true) {
            const auto &msg = msgQueue.front();

            if(std::holds_alternative<int>(msg)) {
                std::cout << "Integer received with value: " << std::get<int>(msg) << std::endl;
            }
            else if(std::holds_alternative<float>(msg)) {
                std::cout << "Float received with value: " << std::get<float>(msg) << std::endl;
            }
            else if(std::holds_alternative<std::string>(msg)) {
                std::cout << "String received with value: " << std::get<std::string>(msg) << std::endl;
            }
            else {
                std::cerr << "Couldn't determine what's being hold in variant!" << std::endl;
            }

            msgQueue.pop();
        }
    }

    template<class T>
    void notify(const T& data)
    {
        msgQueue.push(data);
    }

private: /** Members **/
    std::thread task;

    MsgQueue<std::variant<int, float, std::string>> msgQueue;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment