Go 中的通道(channel)机制十分强大,但是理解内在的概念甚至可以使它更强大。实际上,选择缓冲通道或无缓冲通道将改变应用程序的行为和性能。

无缓冲通道

无缓冲通道是在消息发送到通道时需要接收器的通道。声明一个无缓冲通道时,你不需要声明容量。例如:

goroutinefoo
如果容量为零或未设置,则通道将被无缓冲,只有在发送方和接收方都准备就绪时通信才能成功。

这一点,《Effective Go》中描述的也很清晰:

如果通道是无缓冲的,发送者将被阻塞,直到接收者接收到值。

通道的内部描绘可以给我们更多关于此行为的有趣的细节

无缓冲通道内部结构

channelruntimechan.go
recvqsendqwaitq.sudog

下面是我们前面示例的工作流:

foosudogfoosendqsendqmemmovesudogsudog

正如我们在工作流中再次看到的,goroutine 必须切换到等待,直到接收器可用为止。但是,如果需要,这种阻塞行为可以通过缓冲通道避免。

缓冲通道内部结构

稍微改动之前的例子,以添加一个缓冲区:

hchan

buffer(缓冲)由以下五个属性组成:

qcountdataqsizbufsendxrecvx
sendxrecvx

这个循环队列允许我们在缓冲区中维护一个顺序,而不需要在其中一个元素从缓冲区弹出时不断移动元素。

recvx

由于缓冲区大小不足造成的延迟



我们在通道创建期间定义的缓冲区大小可能会极大地影响性能。我使用扇出模式来密集使用通道,以查看不同缓冲区大小的影响。以下是一些压力测试:

total
benchstat

一个适当大小的缓冲区确实可以使您的应用程序更快!让我们跟踪分析基准测试,以确定延迟在哪里。

追踪延迟

跟踪基准测试将使您访问同步阻塞概要文件,该概要文件显示等待同步原语的 goroutines 阻塞位于何处。Goroutines 在同步过程中花费了 9ms 的时间来等待无缓冲通道的值,而 50 大小的缓冲区只等待 1.9ms:

由于缓冲的存在,来自发送值的等待延迟减小了 5 倍:

我们现在确实证实了我们以前的怀疑。缓冲区的大小对应用程序的性能有重要影响。