spdlog-mpmc_bounded_q.h 无锁队列源码分析

来源:互联网 发布:java byte转16进制 编辑:程序博客网 时间:2024/06/05 11:38

spdlog–mpmc_bounded_q.h 无锁队列分析

spdlog中调用了mpmc_bounded_q.h无锁队列实现异步写日志。

构造函数

构造函数传入buffer最大值,并初始化了buffer数组buffer_以及buffer_mask_

  • buffer_size必须是2的幂次方。
  • 设置每个buffer[i]的值为其序号。
  • 设置入队、出队位置为0。
    mpmc_bounded_queue(size_t buffer_size)        :max_size_(buffer_size),         buffer_(new cell_t [buffer_size]),         buffer_mask_(buffer_size - 1)    {        //queue size must be power of two        if(!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0)))            throw spdlog_ex("async logger queue size must be power of two");        for (size_t i = 0; i != buffer_size; i += 1)            buffer_[i].sequence_.store(i, std::memory_order_relaxed);        enqueue_pos_.store(0, std::memory_order_relaxed);        dequeue_pos_.store(0, std::memory_order_relaxed);    }

入队函数 bool enqueue(T&& data)

  1. 获取插入的位置pos = enqueue_pos_
  2. 获取pos处的buffer_, 即cell_
  3. 判断pos是否等于cell_->sequence_
  4. 若相等,尝试占领pos这个位置(enqueue_pos_.compare_exchange_weak),让enqueue_pos_加一,跳出循环
  5. cell_->sequence_ < pos, 队列中保存的数据已达到max_size_,不入队
  6. cell_->sequence_ > pos,说明cell_处已经被写入数据,更新pos,重新进入第2步

疑问

cell->sequence_.store(pos + 1, std::memory_order_release);,这里困扰我一阵,为什么要将cell的sequence设为pos+1?

个人见解

我觉得主要作用是标记pos处已经放置数据了。若其他线程获得相同的pos,当其再比较pos和sequence时将不会再相等,就不会再次在相同的pos处写入数据。另外,此处的pos+1和出队时的判断intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1);相对应。

    bool enqueue(T&& data) // 传入待插入数据的右值引用    {        cell_t* cell;        size_t pos = enqueue_pos_.load(std::memory_order_relaxed); // 获取插入的位置        for (;;)  //一直循环,直到enqueue_pos_.compare_exchange_weak返回true        {            cell = &buffer_[pos & buffer_mask_];  // 取出一个buffer_            size_t seq = cell->sequence_.load(std::memory_order_acquire); // 取出buffer_自己的序号            intptr_t dif = (intptr_t)seq - (intptr_t)pos;            if (dif == 0) // 如果是同一位置            {                if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed))  // compare函数作用:再次比较enqueue_pos_和pos,如果相同,将enqueue_pos_修改为pos+1,返回true;否则,说明enqueue_pos_已发生改变,其他线程已插入数据,将pos修改为enqueue_pos_,返回false。                    break;            }            else if (dif < 0)  // 队列溢出,保存的数据已达到max_size_            {                return false;            }            else            {                pos = enqueue_pos_.load(std::memory_order_relaxed);            }        }        cell->data_ = std::move(data); // 将数据保存下来        cell->sequence_.store(pos + 1, std::memory_order_release); // 将此cell的sequence设为自己的pos+1。        return true;    }

出队函数 bool dequeue(T& data)

  1. 获取出队位置dequeue_pos_
  2. 获取pos处的buffer_,即cell_
  3. 判断pos + 1是否等于cell_->sequence_
  4. 若相等,尝试占领pos这个位置(dequeue_pos_.compare_exchange_weak),让dequeue_pos_加一,跳出循环
  5. cell_->sequence_ < pos + 1, // 队列为空,返回false
  6. cell_->sequence_ > pos + 1,说明cell_处数据已经出队,更新pos,重新进入第2步
    bool dequeue(T& data)  // 传入的是引用,用以保存出队的数据    {        cell_t* cell;        size_t pos = dequeue_pos_.load(std::memory_order_relaxed);  // 获取出队位置        for (;;)        {            cell = &buffer_[pos & buffer_mask_];  // 获取出队位置buffer_            size_t seq =                cell->sequence_.load(std::memory_order_acquire);  // buffer_自己的序号            intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1);            if (dif == 0)  // 如果是同一位置            {                if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed))  // compare函数作用:再次比较dequeue_pos_和pos,如果相同,将dequeue_pos_修改为pos+1,返回true;否则,说明dequeue_pos_已发生改变,其他线程已删除数据,将pos修改为enqueue_pos_,返回false。                    break;            }            else if (dif < 0)  // 队列为空,返回false                return false;            else                pos = dequeue_pos_.load(std::memory_order_relaxed);        }        data = std::move(cell->data_);        cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release);  // 将此cell的序号改为最后一个,即将此cell移动到队列最后面        return true;    }

队列大小 size_t approx_size()

此函数供外部调用,并没有在出队、入队函数中调用到,用以返回当前队列的实际长度。

    size_t approx_size()    {        size_t first_pos = dequeue_pos_.load(std::memory_order_relaxed);        size_t last_pos = enqueue_pos_.load(std::memory_order_relaxed);        if (last_pos <= first_pos)            return 0;        auto size = last_pos - first_pos;        return size < max_size_ ? size : max_size_;    }

完整代码如下:

/*A modified version of Bounded MPMC queue by Dmitry Vyukov.Original code from:http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queuelicensed by Dmitry Vyukov under the terms below:Simplified BSD licenseCopyright (c) 2010-2011 Dmitry Vyukov. All rights reserved.Redistribution and use in source and binary forms, with or without modification,are permitted provided that the following conditions are met:1. Redistributions of source code must retain the above copyright notice, this list ofconditions and the following disclaimer.2. Redistributions in binary form must reproduce the above copyright notice, this listof conditions and the following disclaimer in the documentation and/or other materialsprovided with the distribution.THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIEDWARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENTSHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOTLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OFLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCEOR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IFADVISED OF THE POSSIBILITY OF SUCH DAMAGE.The views and conclusions contained in the software and documentation are those of the authors andshould not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov.*//*The code in its current form adds the license below:Copyright(c) 2015 Gabi Melman.Distributed under the MIT License (http://opensource.org/licenses/MIT)*/#pragma once#include "spdlog/common.h"#include <atomic>#include <utility>namespace spdlog{namespace details{template<typename T>class mpmc_bounded_queue{public:    using item_type = T;    mpmc_bounded_queue(size_t buffer_size)        :max_size_(buffer_size),         buffer_(new cell_t [buffer_size]),         buffer_mask_(buffer_size - 1)    {        //queue size must be power of two        if(!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0)))            throw spdlog_ex("async logger queue size must be power of two");        for (size_t i = 0; i != buffer_size; i += 1)            buffer_[i].sequence_.store(i, std::memory_order_relaxed);        enqueue_pos_.store(0, std::memory_order_relaxed);        dequeue_pos_.store(0, std::memory_order_relaxed);    }    ~mpmc_bounded_queue()    {        delete [] buffer_;    }    bool enqueue(T&& data)    {        cell_t* cell;        size_t pos = enqueue_pos_.load(std::memory_order_relaxed);        for (;;)        {            cell = &buffer_[pos & buffer_mask_];            size_t seq = cell->sequence_.load(std::memory_order_acquire);            intptr_t dif = (intptr_t)seq - (intptr_t)pos;            if (dif == 0)            {                if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed))                    break;            }            else if (dif < 0)            {                return false;            }            else            {                pos = enqueue_pos_.load(std::memory_order_relaxed);            }        }        cell->data_ = std::move(data);        cell->sequence_.store(pos + 1, std::memory_order_release);        return true;    }    bool dequeue(T& data)    {        cell_t* cell;        size_t pos = dequeue_pos_.load(std::memory_order_relaxed);        for (;;)        {            cell = &buffer_[pos & buffer_mask_];            size_t seq =                cell->sequence_.load(std::memory_order_acquire);            intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1);            if (dif == 0)            {                if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed))                    break;            }            else if (dif < 0)                return false;            else                pos = dequeue_pos_.load(std::memory_order_relaxed);        }        data = std::move(cell->data_);        cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release);        return true;    }    size_t approx_size()    {        size_t first_pos = dequeue_pos_.load(std::memory_order_relaxed);        size_t last_pos = enqueue_pos_.load(std::memory_order_relaxed);        if (last_pos <= first_pos)            return 0;        auto size = last_pos - first_pos;        return size < max_size_ ? size : max_size_;    }private:    struct cell_t    {        std::atomic<size_t>   sequence_;        T                     data_;    };    size_t const max_size_;    static size_t const     cacheline_size = 64;    typedef char            cacheline_pad_t [cacheline_size];    cacheline_pad_t         pad0_;    cell_t* const           buffer_;    size_t const            buffer_mask_;    cacheline_pad_t         pad1_;    std::atomic<size_t>     enqueue_pos_;    cacheline_pad_t         pad2_;    std::atomic<size_t>     dequeue_pos_;    cacheline_pad_t         pad3_;    mpmc_bounded_queue(mpmc_bounded_queue const&) = delete;    void operator= (mpmc_bounded_queue const&) = delete;};} // ns details} // ns spdlog
原创粉丝点击