【问题标题】:detecting invalid iterators for a ring buffer检测环形缓冲区的无效迭代器
【发布时间】:2014-12-09 07:27:27
【问题描述】:

我正在尝试实现一个环形缓冲区(或循环缓冲区)。与这些实现中的大多数一样,它应该尽可能快速和轻量级,但仍然提供足够的安全性以足够健壮以供生产使用。这是一个很难达到的平衡。特别是我面临以下问题。

我想使用该缓冲区来存储最后 n 个系统事件。随着新事件的出现,最旧的事件被删除。然后我的软件的其他部分可以访问这些存储的事件并按照自己的节奏处理它们。一些系统可能会在事件到达时几乎一样快地消耗事件,而其他系统可能只是偶尔检查。每个系统都会将迭代器存储到缓冲区中,以便它们知道上次检查时离开的位置。这没有问题,只要他们检查得足够频繁,但尤其是速度较慢的系统可能经常会发现自己有一个旧迭代器,该迭代器指向一个缓冲区元素,该元素已被覆盖而无法检测到。

是否有一种很好的(不太昂贵的)方法来检查任何给定的迭代器是否仍然有效?

到目前为止我想出的东西:

  • 保留所有迭代器的列表并存储它们的有效状态(相当昂贵)
  • 不仅在调用系统中存储迭代器,还在缓冲区客户端中存储指向元素的副本。在每次访问时,检查元素是否仍然相同。这可能是不可靠的。如果元素已被相同元素覆盖,则无法检查它是否已更改。此外,找到检查元素的好方法的责任在于客户,这在我看来并不理想。

许多环形缓冲区实现根本不关心这一点,或者使用单读单写习语,其中读取正在删除。

【问题讨论】:

  • 您预计有多少消费者?

标签: c++ iterator containers


【解决方案1】:

不存储值,而是存储 (value, sequence_num) 对。当您推送新的value 时,请始终确保它使用不同的sequence_numsequence_num 可以使用单调递增的整数。

然后,迭代器记住它上次查看的元素的sequence_num。如果不匹配,则会被覆盖。

【讨论】:

  • 我会使用诸如“序列号”之类的术语而不是“年龄”,但无论如何+1。
  • 你的建议更好;我将“age”重命名为“sequence_num”。谢谢。
  • 或者更好:迭代器是序列号。然后你的缓冲区只需要跟踪它当前持有的最低序列号,以及它所在的位置。
  • @SanderDeDycker,这是个好建议;您应该在单独的答案中对其进行扩展。
【解决方案2】:

Roger Lipscombe's answer 的一个变体是使用序列号作为迭代器。序列号应该以固定步长(例如 1)单调递增(特别注意整数类型溢出时)。

循环缓冲区本身会照常存储数据,并会跟踪它当前包含的最旧的序列号(在尾部位置)。

当取消引用迭代器时,迭代器的序列号会与缓冲区最旧的序列号进行检查。如果它大于或等于(再次特别注意整数溢出),则可以使用简单的索引计算来检索数据。如果较小,则表示数据已被覆盖,应取而代之的是当前的尾数据(相应地更新迭代器的序列号)。

【讨论】:

    【解决方案3】:

    我同意Roger Lipscombe,使用序列号。

    但您不需要存储 (value, sequence_num) 对:只需存储值,并跟踪目前为止的最高序列号。由于它是一个环形缓冲区,因此您可以推断出所有条目的 seq num。

    因此,迭代器只包含一个序列号。

    给定Obj 存储在环形缓冲区中的对象类型,如果使用简单数组,则环形缓冲区将如下所示:

    struct RingBuffer {
        Obj buf[ RINGBUFFER_SIZE ] ;
        size_t idx_last_element ;
        uint32_t seqnum_last_element ;
    
        void Append( const Obj& obj ) { // TODO: Locking needed if multithreaded 
            if ( idx_last_element == RINGBUFFER_SIZE - 1 )
                idx_last_element = 0 ; 
            else 
                ++idx_last_element ;
            buf[ idx_last_element ] = obj ; // copy.
            ++ seqnum_last_element ;
        }
    }
    

    迭代器看起来像这样:

    struct RingBufferIterator {
        const RingBuffer* ringbuf ;
        uint32_t seqnum ;
    
        bool IsValid() { 
            return ringbuf && 
                   seqnum <= ringbuf->seqnum_last_element &&
                   seqnum > ringbuf->seqnum_last_element - RINGBUFFER_SIZE ; //TODO: handle seqnum rollover.
        }
    
        Obj* ToPointer() {
             if ( ! IsValid() ) return NULL ;
             size_t idx = ringbuf->idx_last_element - (ringbuf->seqnum_last_element-seqnum) ; //TODO: handle seqnum rollover.
             // handle wrap around:
             if ( idx < 0 ) return ringbuf->buf + RINGBUFFER_SIZE- idx ;
             return ringbuf->buf + idx ;
       }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-06-23
      • 1970-01-01
      • 1970-01-01
      • 2012-04-04
      • 2021-11-26
      • 2021-02-20
      • 1970-01-01
      • 1970-01-01
      • 2018-10-29
      相关资源
      最近更新 更多