环形缓冲区 -- circular buffer

环形缓冲区有的地方也称之为环形队列。其实很多人都搞不懂环形缓冲区的本质,从而也就不知道在什么场景下应该使用环形缓冲区。当你了解了它的本质你就知道这玩意儿一点都不高级,而且由于它的本质在某些场景下还不能使用。我看了很多网络游戏服务器的源代码,有些源代码中大量使用这玩意儿。比如我打个比方:

在游戏服务器端只要接收到一个来自客户端的连接就生成一个ClientSocket对象来管理和客户端的数据通信。然后在ClientSocket中有一个环形缓冲区对象CircularBuffer对象来存储客户端发往服务器的字节流数据。仔细想想这种情况下有必要使用CircularBuffer对象么?其实用一个普通的字节数组就足够了,因为,ClientSocket只会调用WSARecv(buffer)一次,然后服务器端IOCP底层收到数据并把数据存放到buffer中之后,唤醒一个工作者线程去让ClientSocket对象处理这个buffer中的数据,然后再次调用WSARecv(buffer)来接受下一次数据。仔细想想,整个过程就是一个生产者对应一个消费者,而且是先生产后消费的过程。用一个buffer[8192]的普通缓冲区完全足够了。而且理解方便,便于代码阅读,用那个环形缓冲区简直就是完全没必要。什么?我上面讲的你还不能完全理解?好吧,先讲讲环形缓冲区的本质。


环形缓冲区的本质:把一个缓冲区当成一个圆形的结构,这样当从里面读取数据和插入数据的时候,都不能重新释放空间和重新分配空间,否则怎么可能是圆形的结构呢?对 吧!!!所以他的好处自然就浮现在眼前了:整个软件声明周期都是使用的这个环形缓冲区不用重新释放和分配空间,这样节省了时间提高了效率。当然缺点也浮现在眼前了:

由于对环形缓冲区的空间大小是最开始就定义好的,如果这个大小估计不准确,太大的话浪费空间,太小的话,呵呵,就得重新分配空间装下新的数据,这就和普通缓冲区没区别了。所以,其实环形缓冲区在服务器编程中的大部分情况下都是很少使用的。


所以:只有当存储空间的分配/释放非常频繁 并且确实产生了明显 的影响,你才应该考虑环形缓冲区的使用。否则的话,还是老老实实用最基本、最简单的队列缓冲区 吧。

注意:环形缓冲区一般都是非线程安全的。网上有些无锁的环形缓冲区,但是本人没在项目中正式运用过,不知道是否是真的,在篇末我会给出一个无锁队列的链接,有兴趣的可以研究一下。


一般的环形缓冲区长这个样子:


看上面的图,当前head指向0这个位置,当数据不断填入,填充到11这个位置的时候数据再往后面填,就会覆盖0和0后面的数据。为了解决这个问题就 要判断head和tail指针,在实际使用中,当tail位于head的前面,计算剩余空间等问题上是很麻烦的,所以,网络上的某一位大神在此技术上运用了一个很简单的思路,来解决这个问题。这位大神的技术文章地址是:http://www.codeproject.com/Articles/3479/The-Bip-Buffer-The-Circular-Buffer-with-a-Twist       魔兽世界的私服服务器Ascent中的环形缓冲区就产生自这位大神的思路。这位大神把优化过后的circluar_buffer改名为Bip Buffer.说实话,翻译出来真的很蹩脚。shit~~~~~~


Bip Buffer:

Bip Buffer内部维护个两个区域,或者简单地来说,将传统的环形缓冲区分配的一块内存分裂成两块内存缓冲区来使用,这里的分裂(bi-partite)也是Bip-Buffer前面的Bip这个缩写的由来。两块内存区都是从左往右来写入数据的,在申请缓冲区的时候,当一个缓冲区满了,就会马上从另外一个内存区的起始地点开始写入, 这样就避免了调整头指针的问题(这就是优化的地方,和一般环形缓冲区不同之处)。下面的图例就明确地显示了它是如何工作的:


整个缓冲区开始的时候是空的,没有分裂成两个区域(如图1所示,是调用AllocatedBuffer()函数以后的样子)

然后,当第一次将数据放到缓冲区以后(如图2所示,这是调用了Commit()函数以后的样子),创建了第一个区域(图中的'A'区域)

当有数据继续写入的时候,都会被追加到区域A的数据的后面,使其向右不断伸展(如图3所示)

第一次从缓冲区中读出数据的样子(如图4所示,参见下面的关于DecommitBlock()函数的说明

这种形式的写入和读出过程会持续下去,直到区域到达了缓冲区的末尾(如图4所示)。一旦在区域A左侧的可用空闲空间要大于区域A右侧的空闲空间,第二个区域(我们叫做区域B)就被创建出来,从此以后的内存分配就从这个区域B来分配(如图5所示)。

如果继续使用缓冲区的空间,我们就会看到如图6所示的样子,区域B在不断扩展,将数据追加到区域B的后面。如果用光区域B与区域A之间的空间(如图7所示),那么就真的没有内存可分配了,直到有一些数据从循环缓冲区中读出。

如果从缓冲区中读取更多的数据出来(假设整个区域A的数据都被读取出来),我们完全把区域A中存储的数据用光了,这时候区域A就完全空了,我们就不再需要维护两个独立的区域了,这时,区域B就获得了整个缓冲区,可以继续追加数据,同时也支持从区域B中向外读取数据了(如图8所示)。

如果从缓冲区中读取更多的数据(如图9所示),我们最终又会得到类似图4所示的存储区使用状态,然后继续重复4,5,6,7,8这些状态的切换,完成循环利用缓冲区。


这就是Bip Buffer的思路。等会儿上代码。
关于环形缓冲区的一些文章:
1.环形缓冲区在数字信号方面的应用:http://www.dspguide.com/ch28/2.htm
2.Bip Buffer : http://www.codeproject.com/Articles/3479/The-Bip-Buffer-The-Circular-Buffer-with-a-Twist
3.Ring Buffer:http://c.learncodethehardway.org/book/ex44.html
4.http://blog.csdn.net/program_think/article/details/4040068
5.Bip buffer翻译:http://blog.chinaunix.net/uid-8272118-id-2033337.html
6.传说中的无锁环形队列:http://www.codeproject.com/Articles/153898/Yet-another-implementation-of-a-lock-free-circular
7.http://www.cnblogs.com/chencheng/p/3527692.html

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值