将infinity无锁缓冲区折叠到循环缓冲区时避免冲突

时间:2013-04-24 21:23:59

标签: c++ lock-free circular-buffer

我正在解决FAST协议的两个Feed仲裁问题。 如果你不熟悉它,请不要担心,我的问题实际上非常普遍。但我正在为那些感兴趣的人添加问题描述(你可以跳过它)。


所有UDP源中的数据在两个不同的多播IP上的两个相同的源(A和B)中传播。强烈建议客户端因可能的UDP数据包丢失而接收和处理这两个源。处理两个相同的馈送允许统计上降低分组丢失的可能性。 未在第一次显示消息的特定馈送(A或B)中指定。要仲裁这些订阅源,应使用在Preamble或标签34-MsgSeqNum中找到的消息序列号。利用前导码允许在不解码FAST消息的情况下确定消息序列号。 应使用以下算法处理来自Feed A和B的消息:

  1. 收听Feed A和B
  2. 根据序列号处理消息。
  3. 如果之前已经处理了具有相同序列号的消息,则忽略该消息。
  4. 如果出现序列号中的间隙,则表示两个源(A和B)中的数据包丢失。客户端应启动其中一个恢复过程。但首先客户端应该等待一段合理的时间,由于数据包重新排序,丢失的数据包可能会稍晚一些。 UDP协议不能保证按顺序传送数据包。

    // tcp recover algorithm进一步


  5. 我写了这么简单的课。它预先分配所有必需的类,然后接收特定seqNum的第一个线程可以处理它。另一个线程将在以后放弃它:

    class MsgQueue
    {
    public:
        MsgQueue();
        ~MsgQueue(void);
        bool Lock(uint32_t msgSeqNum);
        Msg& Get(uint32_t msgSeqNum);
        void Commit(uint32_t msgSeqNum);
    private:
        void Process();
        static const int QUEUE_LENGTH = 1000000;
    
        // 0 - available for use; 1 - processing; 2 - ready
        std::atomic<uint16_t> status[QUEUE_LENGTH];
        Msg updates[QUEUE_LENGTH];
    };
    

    实现:

    MsgQueue::MsgQueue()
    {
            memset(status, 0, sizeof(status));
    }
    
    MsgQueue::~MsgQueue(void)
    {
    }
    
    // For the same msgSeqNum should return true to only one thread 
    bool MsgQueue::Lock(uint32_t msgSeqNum)
    {
        uint16_t expected = 0;
        return status[msgSeqNum].compare_exchange_strong(expected, 1);
    }
    
    void MsgQueue::Commit(uint32_t msgSeqNum)
    {
        status[msgSeqNum] = 2;
                Process();
    }
    
        // this method probably should be combined with "Lock" but please ignore! :)
    Msg& MsgQueue::Get(uint32_t msgSeqNum)
    {
        return updates[msgSeqNum];
    }
    
    void MsgQueue::Process()
    {
            // ready packets must be processed, 
    }
    

    用法:

    if (!msgQueue.Lock(seq)) {
        return;
    }
    Msg msg = msgQueue.Get(seq);
    msg.Ticker = "HP"
    msg.Bid = 100;
    msg.Offer = 101;
    msgQueue.Commit(seq);
    

    如果我们假设QUEUE_LENGTH是无穷大,这可以正常工作。因为在这种情况下,一个msgSeqNum =一个updates数组项。

    但是我必须使缓冲区循环,因为它不可能存储整个历史记录(数百万个数据包),并且没有理由这样做。实际上我需要缓冲足够的数据包来重建会话,一旦重建了会话,我就可以删除它们。

    但是使用循环缓冲区会使算法复杂化。例如,假设我们有长度为1000的循环缓冲区。同时我们尝试处理seqNum = 10 000和seqNum = 11 000(这是非常不可能但仍然可能)。这两个数据包都将映射到索引updates的数组0,因此会发生冲突。在这种情况下,缓冲区应该“丢弃”旧数据包并处理新数据包。

    使用locks实现我想要的东西是微不足道的,但是在不同线程使用的循环缓冲区上编写lock-free代码真的很复杂。所以我欢迎任何建议和建议如何做到这一点。谢谢!

1 个答案:

答案 0 :(得分:0)

我不相信你可以使用环形缓冲区。可以在status[]数组中使用散列索引。即,hash = seq % 1000。问题是序列号由网络决定,您无法控制它的排序。您希望根据此序列号锁定。您的数组不需要无限,只需序列号的范围;但这可能比实际更大。

我不确定序列号被锁定时发生了什么。这是否意味着另一个线程正在处理它?如果是这样,您必须维护一个子列表以进行哈希冲突,以解决特定的序列号。

您也可以将数组大小视为2的幂。例如,1024将允许hash = seq & 1023;这应该非常有效。