码迷,mamicode.com
首页 > 编程语言 > 详细

C++11 —— 单生产者/单消费者 的 FIFO 无锁队列

时间:2020-01-01 23:46:14      阅读:110      评论:0      收藏:0      [点我收藏+]

标签:源码   val   操作   cond   return   from   block   cas   members   

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

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

技术图片

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

2. 源码 (xspsc_queue.h)

/**
 * The MIT License (MIT)
 * Copyright (c) 2019, Gaaagaa All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * @file xspsc_queue.h
 * Copyright (c) 2019, Gaaagaa All rights reserved.
 * 
 * @author  :Gaaagaa
 * @date    : 2019-11-29
 * @version : 1.0.0.0
 * @brief   : 实现双线程安全的 单生产者/单消费者 FIFO 队列。
 */

#ifndef __XSPSC_QUEUE_H__
#define __XSPSC_QUEUE_H__

#include <memory>
#include <atomic>
#include <cassert>

////////////////////////////////////////////////////////////////////////////////
// x_spsc_queue_t : single producer/single consumer FIFO queue

/**
 * @class x_spsc_queue_t
 * @brief 双线程安全的 单生产者/单消费者 FIFO队列。
 * 
 * @param [in ] __object_t    : 队列存储的对象类型。
 * @param [in ] __chunk_size  : 队列中的存储块可容纳对象的数量。
 * @param [in ] __allocator_t : 对象分配器。
 */
template< typename __object_t,
          size_t   __chunk_size,
          typename __allocator_t = std::allocator< __object_t > >
class x_spsc_queue_t : protected __allocator_t
{
    static_assert(__chunk_size >= 4,
                  "__chunk_size size value must be greater than or equal to 4!");

    // common data types
public:
    typedef __object_t value_type;
    using x_object_t = __object_t;

private:
    /**
     * @struct x_chunk_t
     * @brief  存储对象节点的连续内存块结构体。
     */
    typedef struct x_chunk_t
    {
        x_chunk_t  * xnext_ptr;   ///< 指向后一内存块节点
        x_object_t * xot_array;   ///< 当前内存块中的对象节点数组
    } 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   = __allocator_t;
    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();

        assert(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 & operator = (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)
    {
        assert(!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)
    {
        assert(!empty());
        return m_chk_front->xot_array[m_pos_front];
    }

    /**********************************************************/
    /**
     * @brief 返回队列前端对象。
     */
    inline const x_object_t & front(void) const
    {
        assert(!empty());
        return m_chk_front->xot_array[m_pos_front];
    }

    /**********************************************************/
    /**
     * @brief 返回队列后端对象。
     */
    inline x_object_t & back(void)
    {
        assert(!empty());
        return m_chk_back->xot_array[m_pos_back];
    }

    /**********************************************************/
    /**
     * @brief 返回队列后端对象。
     */
    inline const x_object_t & back(void) const
    {
        assert(!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);
        assert(nullptr != xchunk_ptr);

        if (nullptr != xchunk_ptr)
        {
            xchunk_ptr->xot_array = x_allocator_t::allocate(__chunk_size);
            assert(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, __chunk_size);
            }

            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 == __chunk_size)
        {
            x_chunk_ptr_t xchunk_ptr = m_chk_front;
            m_chk_front = m_chk_front->xnext_ptr;
            assert(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 == __chunk_size)
        {
            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. 使用示例


#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;
}

C++11 —— 单生产者/单消费者 的 FIFO 无锁队列

标签:源码   val   操作   cond   return   from   block   cas   members   

原文地址:https://www.cnblogs.com/Gaaagaa/p/12130787.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!