什么?无限缓冲的队列(一)?

介绍

事情的原由是前几周看到鸟窝写了一篇关于实现无限缓冲 channel 的文章,当时忙着和小姐姐聊天没看,今天想起来了。数组

不过这篇文章不会涉及到鸟窝本身实现的 chanx,咱们会在下一篇提到。app

咱们都知道,channel 有两种类型:无缓冲和有缓冲的。函数

当咱们建立一个有缓冲的通道并指定了容量,那么在这个通道的生命周期内,咱们将再也没法改变它的容量。oop

有时候,咱们并不知道也没法预估写入通道的数量规模。若是此时通道的写入速度远远超过读取速度,那么必然会在某个时间点塞满通道,致使写入阻塞。
好比以前我翻译的一篇文章 使用 Go 每分钟处理百万请求 中,做者就出现处理速度太慢,致使通道塞满,其余请求被阻塞,响应时间慢慢增长。post

此时有人就会提到,能不能提供一个无限缓冲(Unbounded or Unlimited)的通道。测试

这个问题早在 2017 年就有人提过 issues,最终 go 官方没有实现这个提案。this

不过,这个 issues 下面总共产生了 67 个 comments,评论很精彩。
imagespa

好比有人提到:翻译

cznic:Unlimited capacity channels ask for a machine with unlimited memory.

rsc:The limited capacity of channels is an important source of backpressure in a set of communicating goroutines. It is typically a mistake to use an unbounded channel, because you lose that backpressure. If one goroutine falls sufficiently behind, you usually want to take some action in response, not just queue its messages forever. The appropriate response varies by situation: maybe you want to drop messages, maybe you want to keep summary messages, maybe you want to take different responses as the goroutine falls further and further behind. Making it trivial to reach for unbounded channels keeps developers from thinking about this, which I believe is a strong disadvantage.

那么如何实现一个无限缓冲的通道呢?code

针对这类需求,有不少版本的实现,咱们来看其中的一个实现。鸟窝的 chanx 就是在这个基础上作修改的。

咱们一步步还原它的实现,这其中还能知道做者的思考过程。

代码

初版,

image

MakeInfinite 函数返回两个通道,第一个用于数据的写入,第二个用于数据的读取。

注意看这里的细节,在返回的时候就约束了通道的操做类型:一个只写,一个只读,这样避免了用户破坏通道的操做流程。
这里面的代码也简单,只要写入通道 in 未被关闭,那么就把从 in 通道中读取的值 appendinQueue 切片中。
inQueue 在这里就是实现无限缓冲的中间层。

而后有个 test。

image

image

当走到第二个 case 的时候,因为 inQueue 一开始是空的,那么必然会出现 index out
不只是一开始,在运行中,若是读取比写入快,那么必然也会致使相同的状况。

image
image

inQueue 没有值的时候,咱们把 nil 也写入到通道,
而后测试代码中咱们从 out channel 读取数值试图把值断言 int 失败了。 那么,当队列中没有数据时,咱们不该该写入 out 通道。

image
做者使用了一个技巧,若是 inQueue 没有数据,那么尝试写入一个 nil 通道将永远阻塞。
一般,永久阻塞是一个很差的行为,可是这个是包含在 select 语句中的,因此问题不大。

image

还有问题。缘由很简单,咱们再发送完数据就立刻关闭了 in 通道。随后 break loop。接下来关闭 out 通道,程序运行结束。
此时 inQueue 还有值未被取出。

只要写比读快,那么就永远存在这个问题。咱们须要保证在通道关闭的时候,inQueue 已为空。
image

总结

上面是如何实现一个无限缓冲的 channel

借助了一个临时存储数据的中间层。

上面的实现有没有哪些地方能够改进?

inQueue 做为中间层,本质上是一个切片。明明 inQueue 已经扩容到很大的值了,可是并无对应的 reset。会致使 inQueue 指向还在底层数组靠后的位置,并不能复用数组前面的空间,形成浪费。

chanx 是咋么改进的?

下一篇

相关文章
相关标签/搜索