C++11 实现 2 种线程安全的同步队列

1,265 阅读2分钟

「这是我参与11月更文挑战的第 6 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 10 篇文章

参考原文地址: (原创)C++ 同步队列

我对文章的格式和错别字进行了调整,并在他的基础上,根据我自己的理解把重点部分进一步解释完善(原作者的代码注释甚少)。以下是正文。

同步队列作为一个线程安全的数据共享区,经常用于线程之间数据读取。其核心是要用到 list、锁和条件变量,条件变量的作用是在队列满了的时候进行等待或者队列空了的时候发出通知。

简单的线程安全的同步队列(无上限)

#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>
using namespace std;

/// @note 通过模板实现,支持多种数据类型
template <typename T>
class SimpleSyncQueue
{
public:
    /// @note 将数据放入队列
    /// 并发出(队列不为空的)通知
    void Put(const T &x)
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        m_queue.push_back(x);
        m_notEmpty.notify_one();
    }
    
    /// @note 从队列中取数据
    /// 会先等待阻塞直到队列不为空
    void Take(T &x)
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        m_notEmpty.wait(locker, [this]
                        { return !m_queue.empty(); });
        x = m_queue.front();
        m_queue.pop_front();
    }

private:
    std::list<T> m_queue; ///< 同步队列
    std::mutex m_mutex;   ///< 互斥锁
    std::condition_variable_any m_notEmpty; ///< 队列不为空的条件变量
}; 

上面这个例子,队列理论上可以存访无限的数据,现在我们再看看如何设计一个带上限的同步队列

改进的线程安全的同步队列(有上限)

#include <list>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <iostream>
using namespace std;

template <typename T>
class SyncQueue
{
public:
    SyncQueue(int maxSize) : m_maxSize(maxSize), m_needStop(false)
    {
    }

    void Put(const T &x)
    {
        Add(x);
    }

    void Put(T &&x)
    {
        Add(std::forward<T>(x));
    }

    /// @note 取出整个队列
    void Take(std::list<T> &list)
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        m_notEmpty.wait(locker, [this]
                        { return m_needStop || NotEmpty(); });

        if (m_needStop)
            return;
        list = std::move(m_queue); ///< 移动语义
        m_notFull.notify_one();
    }

    void Take(T &t)
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        m_notEmpty.wait(locker, [this]
                        { return m_needStop || NotEmpty(); });

        if (m_needStop)
            return;
        t = m_queue.front();
        m_queue.pop_front();
        m_notFull.notify_one();
    }

    /// @note 中断入队或者出队
    void Stop()
    {
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            m_needStop = true;
        }
        m_notFull.notify_all();
        m_notEmpty.notify_all();
    }

    bool Empty()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.empty();
    }

    bool Full()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.size() == m_maxSize;
    }

    size_t Size()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.size();
    }

    /// @note 没有加锁
    int Count()
    {
        return m_queue.size();
    }

private:
    /// @note 判断队列是否不满
    /// 这里之所以没有加锁,是因为调用外部已经加锁
    bool NotFull() const
    {
        bool full = m_queue.size() >= m_maxSize;
        if (full)
            cout << "full, waiting,thread id: " << this_thread::get_id() << endl;
        return !full;
    }

    /// @note 判断队列是否不为空
    /// 这里之所以没有加锁,是因为调用外部已经加锁
    bool NotEmpty() const
    {
        bool empty = m_queue.empty();
        if (empty)
            cout << "empty,waiting,thread id: " << this_thread::get_id() << endl;
        return !empty;
    }

    /// @note 模板函数(注意命名区别于 T)
    template <typename F>
    void Add(F &&x)
    {
        std::unique_lock<std::mutex> locker(m_mutex);
        /// @note 如果 STOP 或者队列不满则不用阻塞
        m_notFull.wait(locker, [this]
                       { return m_needStop || NotFull(); });
        if (m_needStop)
            return;

        m_queue.push_back(std::forward<F>(x)); ///< 入队
        m_notEmpty.notify_one(); ///< 通知队列不为空
    }

private:
    std::list<T> m_queue;               ///< 同步队列
    std::mutex m_mutex;                 ///< 互斥量和条件变量结合起来使用
    std::condition_variable m_notEmpty; ///< 不为空的条件变量
    std::condition_variable m_notFull;  ///< 没有满的条件变量
    int m_maxSize;                      ///< 同步队列最大的 size

    bool m_needStop;                    ///< 停止的标志
};