Ring Buffer 原理

环形缓冲区(RingBuffer)是一种常见的线程间数据同步工具,采用FIFO模型,适用于固定容量的数据交换。与线性队列不同,环形缓冲区在数据消费后不需要移动元素,而线性队列则需要。它包含4个指针,用于跟踪读写位置。在多线程环境下,通过适当的同步机制,RingBuffer能高效支持多个生产者和消费者。消费者通过ConsumerBarrier等待并获取新数据,实现并发数据处理,减少了延迟并允许多个消费者同时读取,无需额外的锁机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Ring Buffer原理: 

环形缓冲区(ring buffer),也是环形队列(ring queue) 多用于2个线程之间传递数据,是标准的先入先出(FIFO)模型可以用于2个线程中共享数据的同步,而且必须遵循1个线程push in,另一线程pull out的原则圆形缓冲区适合于事先明确了缓冲区的最大容量的情形。扩展一个圆形缓冲区的容量,需要搬移其中的数据。因此一个缓冲区如果需要经常调整其容量,用链表实现更为合适

环形队列[Ring Buffer]与线性队列的区别:

  1. 圆形缓冲区当一个数据元素被用掉后,其余数据元素不需要移动其存储位置非圆形缓冲区(普通线性的队列)在用掉一个数据元素后,其余数据元素需要向前搬移。换句话说,圆形缓冲区适合实现先进先出缓冲区,非圆形缓冲区适合后进先出缓冲区
  2. 线性队列按顺序依次排列数据,而循环队列通过将最后一个元素连接回第一个元素来排列类似于圆形的数据。
  3. Ring Buffer 与传统队列的区别是:buffer 里的对象不会被销毁-它们留在那儿直到下次被覆盖写入

圆形缓冲区工作机制:

一般的,圆形缓冲区需要4个指针

  1. 在内存中实际开始位置;
  2. 在内存中实际结束位置,也可以用缓冲区长度代替;
  3. 存储在缓冲区中的有效数据的开始位置(读指针)[可以指定到底要读取数组中哪部分数据]
  4. 存储在缓冲区中的有效数据的结尾位置(写指针)。

圆形缓冲区的读写指针:

                        线程 A                  媒介                    线程 B

                        data in --> ring buffer/queue --> data out

                        写指针[写数据]                             读指针[读数据]

环形缓冲区(Ring Buffer)初始态

向环形缓冲区(Ring Buffer)中添加一个数据

向环形缓冲区(Ring Buffer)中添加一个数据,并读取一个数据

  1. 读指针指向环形缓冲区中可读的数据写指针指向环形缓冲区中可写的缓冲区。通过移动读指针和写指针就可以实现缓冲区的数据读取和写入。在通常情况下,环形缓冲区的读用户仅仅会影响读指针,而写用户仅仅会影响写指针。如果仅仅有一个读用户和一个写用户,那么不需要添加互斥保护机制就可以保证数据的正确性。如果有多个读写用户访问环形缓冲区,那么必须添加互斥保护机制来确保多个用户互斥访问环形缓冲区。
  2. 如果读写指针的值相等,则缓冲区为空如果读写指针相差n,则缓冲区为满,这可以用条件表达式(写指针 == (读指针 异或 缓冲区长度))来判断。

生产者,消费者与ConsumerBarrier模型:

假设通过生产者已经把数据写入到Ring Buffer了,怎样从Ring Buffer读出这些数据呢?

消费者(Consumer)是一个想从Ring Buffer里读取数据的线程,它可以访问ConsumerBarrier对象——这个对象由RingBuffer创建并且代表消费者与RingBuffer进行交互。就像Ring Buffer显然需要一个序号才能找到下一个可用节点一样,消费者也需要知道它将要处理的序号——每个消费者都需要找到下一个它要访问的序号。在上面的例子中,消费者处理完了Ring Buffer里序号8之前(包括8)的所有数据,那么它期待访问的下一个序号是9。

消费者可以调用ConsumerBarrier对象的waitFor()方法,传递它所需要的下一个序号.

        final long availableSeq = consumerBarrier.waitFor(nextSequence);

ConsumerBarrier返回RingBuffer的最大可访问序号——在上面的例子中是12。ConsumerBarrier有一个WaitStrategy方法来决定它如何等待这个序号,我现在不会去描述它的细节,代码的注释里已经概括了每一种WaitStrategy的优点和缺点 。

接下来,消费者会一直原地停留,等待更多数据被写入Ring Buffer。并且,一旦数据写入后消费者会收到通知——节点9,10,11和12 已写入。现在序号12到了,消费者可以让ConsumerBarrier去拿这些序号节点里的数据了。

拿到了数据后,消费者(Consumer)会更新自己的标识(cursor)。

你应该已经感觉得到,这样做是怎样有助于平缓延迟的峰值了——以前需要逐个节点地询问“我可以拿下一个数据吗?现在可以了么?现在呢?”,消费者(Consumer)现在只需要简单的说“当你拿到的数字比我这个要大的时候请告诉我”,函数返回值会告诉它有多少个新的节点可以读取数据了。因为这些新的节点的确已经写入了数据(Ring Buffer本身的序号已经更新),而且消费者对这些节点的唯一操作是读而不是写,因此访问不用加锁。这太好了,不仅代码实现起来可以更加安全和简单,而且不用加锁使得速度更快。

另一个好处是——你可以用多个消费者(Consumer)去读同一个RingBuffer ,不需要加锁,也不需要用另外的队列来协调不同的线程(消费者)。这样你可以在Disruptor的协调下实现真正的并发数据处理

参考:Ring Buffer 有什么特别? - 其他微控制器论坛 - 其他微控制器 - E2E™ 设计支持 (ti.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梅尔文

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值