• 单生产者/单消费者 的 FIFO 无锁队列


      发现 zeromq 的 yqueue_t 模板类,其数据存储理念设计得非常妙。借这一理念,按照 STL 的泛型类 queue 的接口标准,我设计了一个线程安全的 单生产者/单消费者(单线程push/单线程pop) FIFO 队列,以此满足更为广泛的应用。

    1. 数据存储理念的结构图

    type_index

    • 队列的整体结构上,使用链表的方式,将多个固定长度的 chunk 串联起来;
    • 每个 chunk 则可用于存储队列所需要的元素;
    • 增加一个可交换的 chunk 单元,利于内存复用;
    • 队列使用时,支持 单个线程的 push(生产) 和 单个线程 pop(消费)的并发操作(内部并未加锁)。

    2. 源码 (xspsc_queue.h)

    /**
     * @file    xspsc_queue.h
     * <pre>
     * Copyright (c) 2018, Gaaagaa All rights reserved.
     * 
     * 文件名称:xspsc_queue.h
     * 创建日期:2018年12月17日
     * 文件标识:
     * 文件摘要:实现双线程安全的 单生产者/单消费者(single producer/single consumer) FIFO 队列。
     * 
     * 特别声明:x_spsc_queue_t 的设计,主要参考了 zeromq 的 yqueue_t 模板类的数据存储理念。
     * 特别鸣谢:zeromq 开源项目,Lee哥 。
     * 
     * 当前版本:1.0.0.0
     * 作    者:
     * 完成日期:2018年12月17日
     * 版本摘要:
     * 
     * 历史版本:
     * 原作者  :
     * 完成日期:
     * 版本摘要:
     * </pre>
     */
    
    #ifndef __XSPSC_QUEUE_H__
    #define __XSPSC_QUEUE_H__
    
    #include <memory>
    #include <atomic>
    
    ////////////////////////////////////////////////////////////////////////////////
    
    #ifndef ENABLE_XASSERT
    #if ((defined _DEBUG) || (defined DEBUG))
    #define ENABLE_XASSERT 1
    #else // !((defined _DEBUG) || (defined DEBUG))
    #define ENABLE_XASSERT 0
    #endif // ((defined _DEBUG) || (defined DEBUG))
    #endif // ENABLE_XASSERT
    
    #ifndef XASSERT
    #if ENABLE_XASSERT
    #include <assert.h>
    #define XASSERT(xptr)    assert(xptr)
    #else // !ENABLE_XASSERT
    #define XASSERT(xptr)
    #endif // ENABLE_XASSERT
    #endif // XASSERT
    
    ////////////////////////////////////////////////////////////////////////////////
    // x_spsc_queue_t
    
    /**
     * @class x_spsc_queue_t
     * @brief 双线程安全的 单生产者/单消费者 FIFO队列。
     * 
     * @param [in ] _Ty    : 队列存储的对象类型。
     * @param [in ] _Num   : 队列中的存储块可容纳对象的数量。
     * @param [in ] _Alloc : 对象分配器。
     */
    template< typename _Ty, size_t _Num, typename _Alloc = std::allocator< _Ty > >
    class x_spsc_queue_t : protected _Alloc
    {
        static_assert(_Num >= 4, "_Num size value must be greater than or equal to 4!");
    
        // common data types
    public:
        using x_object_t = _Ty;
    
    private:
        /**
         * @struct x_chunk_t
         * @brief  存储对象节点的连续内存块结构体。
         */
        typedef struct x_chunk_t
        {
            x_object_t * xot_array;   ///< 当前内存块中的对象节点数组
            x_chunk_t  * xnext_ptr;   ///< 指向后一内存块节点
        } x_chunk_t;
    
    #ifdef _MSC_VER
        using ssize_t = std::intptr_t;
    #endif // _MSC_VER
    
        using x_chunk_ptr_t   = x_chunk_t *;
        using x_atomic_ptr_t  = std::atomic< x_chunk_ptr_t >;
        using x_atomic_size_t = std::atomic< size_t >;
        using x_allocator_t   = _Alloc;
        using x_chunk_alloc_t = typename std::allocator_traits<
                                            x_allocator_t >::template
                                                rebind_alloc< x_chunk_t >;
    
        // constructor/destructor
    public:
        explicit x_spsc_queue_t(void)
            : m_chk_front(nullptr)
            , m_pos_front(0)
            , m_chk_back(nullptr)
            , m_pos_back(0)
            , m_xst_size(0)
            , m_chk_stored(nullptr)
        {
            m_chk_front = m_chk_back = alloc_chunk();
        }
    
        ~x_spsc_queue_t(void)
        {
            while (size() > 0)
                pop();
    
            XASSERT(m_chk_front == m_chk_back);
            free_chunk(m_chk_front);
    
            free_chunk(m_chk_stored.exchange(nullptr));
    
            m_chk_front = nullptr;
            m_pos_front = 0;
            m_chk_back  = nullptr;
            m_pos_back  = 0;
        }
    
        x_spsc_queue_t(x_spsc_queue_t && xobject) = delete;
        x_spsc_queue_t(const x_spsc_queue_t & xobject) = delete;
        x_spsc_queue_t & operator=(const x_spsc_queue_t & xobject) = delete;
    
        // public interfaces
    public:
        /**********************************************************/
        /**
         * @brief 当前队列中的对象数量。
         */
        inline size_t size(void) const
        {
            return m_xst_size;
        }
    
        /**********************************************************/
        /**
         * @brief 判断队列是否为空。
         */
        inline bool empty(void) const
        {
            return (0 == size());
        }
    
        /**********************************************************/
        /**
         * @brief 向队列后端压入一个对象。
         */
        void push(const x_object_t & xobject)
        {
            move_back_pos();
            x_allocator_t::construct(
                &m_chk_back->xot_array[m_pos_back], xobject);
            m_xst_size.fetch_add(1);
        }
    
        /**********************************************************/
        /**
         * @brief 向队列后端压入一个对象。
         */
        void push(x_object_t && xobject)
        {
            move_back_pos();
            x_allocator_t::construct(
                &m_chk_back->xot_array[m_pos_back],
                std::forward< x_object_t >(xobject));
            m_xst_size.fetch_add(1);
        }
    
        /**********************************************************/
        /**
         * @brief 从队列前端弹出一个对象。
         */
        void pop(void)
        {
            XASSERT(!empty());
            m_xst_size.fetch_sub(1);
            x_allocator_t::destroy(&m_chk_front->xot_array[m_pos_front]);
            move_front_pos();
        }
    
        /**********************************************************/
        /**
         * @brief 返回队列前端对象。
         */
        inline x_object_t & front(void)
        {
            XASSERT(!empty());
            return m_chk_front->xot_array[m_pos_front];
        }
    
        /**********************************************************/
        /**
         * @brief 返回队列前端对象。
         */
        inline const x_object_t & front(void) const
        {
            XASSERT(!empty());
            return m_chk_front->xot_array[m_pos_front];
        }
    
        /**********************************************************/
        /**
         * @brief 返回队列后端对象。
         */
        inline x_object_t & back(void)
        {
            XASSERT(!empty());
            return m_chk_back->xot_array[m_pos_back];
        }
    
        /**********************************************************/
        /**
         * @brief 返回队列后端对象。
         */
        inline const x_object_t & back(void) const
        {
            XASSERT(!empty());
            return m_chk_back->xot_array[m_pos_back];
        }
    
        // internal invoking
    private:
        /**********************************************************/
        /**
         * @brief 申请一个存储对象节点的内存块。
         */
        x_chunk_ptr_t alloc_chunk(void)
        {
            x_chunk_alloc_t xchunk_allocator(*(x_allocator_t *)this);
    
            x_chunk_ptr_t xchunk_ptr = xchunk_allocator.allocate(1);
            XASSERT(nullptr != xchunk_ptr);
    
            if (nullptr != xchunk_ptr)
            {
                xchunk_ptr->xot_array = x_allocator_t::allocate(_Num);
                XASSERT(nullptr != xchunk_ptr->xot_array);
    
                if (nullptr != xchunk_ptr->xot_array)
                {
                    xchunk_ptr->xnext_ptr = nullptr;
                }
                else
                {
                    xchunk_allocator.deallocate(xchunk_ptr, 1);
                    xchunk_ptr = nullptr;
                }
            }
    
            return xchunk_ptr;
        }
    
        /**********************************************************/
        /**
         * @brief 释放一个存储对象节点的内存块。
         */
        void free_chunk(x_chunk_ptr_t xchunk_ptr)
        {
            if (nullptr != xchunk_ptr)
            {
                if (nullptr != xchunk_ptr->xot_array)
                {
                    x_allocator_t::deallocate(xchunk_ptr->xot_array, _Num);
                }
    
                x_chunk_alloc_t xchunk_allocator(*(x_allocator_t *)this);
                xchunk_allocator.deallocate(xchunk_ptr, 1);
            }
        }
    
        /**********************************************************/
        /**
         * @brief 将前端位置向后移(该接口仅由 pop() 接口调用)。
         */
        void move_front_pos(void)
        {
            if (++m_pos_front == _Num)
            {
                x_chunk_ptr_t xchunk_ptr = m_chk_front;
                m_chk_front = m_chk_front->xnext_ptr;
                XASSERT(nullptr != m_chk_front);
                m_pos_front = 0;
    
                free_chunk(m_chk_stored.exchange(xchunk_ptr));
            }
        }
    
        /**********************************************************/
        /**
         * @brief 将后端位置向后移(该接口仅由 push() 接口调用)。
         */
        void move_back_pos(void)
        {
            if (++m_pos_back == _Num)
            {
                x_chunk_ptr_t xchunk_ptr = m_chk_stored.exchange(nullptr);
                if (nullptr != xchunk_ptr)
                {
                    xchunk_ptr->xnext_ptr = nullptr;
                    m_chk_back->xnext_ptr = xchunk_ptr;
                }
                else
                {
                    m_chk_back->xnext_ptr = alloc_chunk();
                }
    
                m_chk_back = m_chk_back->xnext_ptr;
                m_pos_back = 0;
            }
        }
    
        // data members
    protected:
        x_chunk_ptr_t    m_chk_front;  ///< 内存块链表的前端块
        ssize_t          m_pos_front;  ///< 队列中的前端对象位置
        x_chunk_ptr_t    m_chk_back;   ///< 内存块链表的后端块
        ssize_t          m_pos_back;   ///< 队列中的后端对象位置
        x_atomic_size_t  m_xst_size;   ///< 队列中的有效对象数量
        x_atomic_ptr_t   m_chk_stored; ///< 用于保存临时内存块(备用缓存块)
    };
    
    ////////////////////////////////////////////////////////////////////////////////
    
    #endif // __XSPSC_QUEUE_H__
    
    

    3. 使用示例

    /**
     * @file    main.cpp
     * <pre>
     * Copyright (c) 2019, Gaaagaa All rights reserved.
     * 
     * 文件名称:main.cpp
     * 创建日期:2019年02月07日
     * 文件标识:
     * 文件摘要:单生产者/单消费者(single producer/single consumer)FIFO 队列 的测试程序。
     * 
     * 当前版本:1.0.0.0
     * 作    者:
     * 完成日期:2019年02月07日
     * 版本摘要:
     * 
     * 取代版本:
     * 原作者  :
     * 完成日期:
     * 版本摘要:
     * </pre>
     */
    
    #include "xspsc_queue.h"
    #include <iostream>
    #include <thread>
    #include <chrono>
    
    #include <list>
    
    ////////////////////////////////////////////////////////////////////////////////
    
    int main(int argc, char * argv[])
    {
        using x_int_queue_t = x_spsc_queue_t< int, 8 >;
    
        x_int_queue_t spsc;
    
        std::cout << "sizeof(x_int_queue_t) : " << sizeof(x_int_queue_t) << std::endl;
    
        bool b_push_finished = false;
        std::thread xthread_in([&spsc, &b_push_finished](void) -> void
        {
            for (int i = 1; i < 10000; ++i)
            {
                spsc.push(i);
                std::this_thread::sleep_for(std::chrono::milliseconds(1));
            }
    
            b_push_finished = true;
        });
    
        std::thread xthread_out([&spsc, &b_push_finished](void) -> void
        {
            while (true)
            {
                if (!spsc.empty())
                {
                    std::cout << spsc.size() << " : " << spsc.front() << std::endl;
                    spsc.pop();
                    std::this_thread::sleep_for(std::chrono::milliseconds(1));
                }
                else if (b_push_finished)
                {
                    break;
                }
            }
        });
    
        if (xthread_in.joinable())
        {
            xthread_in.join();
        }
    
        if (xthread_out.joinable())
        {
            xthread_out.join();
        }
    
        return 0;
    }
    
    
  • 相关阅读:
    使用 ESP8266 制作 WiFi 干扰器
    苹果手机连接Wifi认证机制
    TK2 USB修复
    WiFi其他方法和WiFi事件响
    获取与esp8266连接的客户端的Mac地址 IP 端口 控制停止等问题
    WiFi其他方法和WiFi事件响应
    Arduino内部网页代理,网页穿透,公网访问Arduino内部网页
    ESP8266远程OTA升级
    分级基金及套利策略:申购套利、赎回套利、低折套利
    maven安装
  • 原文地址:https://www.cnblogs.com/VxGaaagaa/p/11110492.html
Copyright © 2020-2023  润新知