Random Thoughts

Recent content on Random Thoughts

马上订阅 Random Thoughts RSS 更新: https://blog.joway.io/index.xml

重新思考 Go:Channel 不是「消息队列」

2024年3月31日 08:00

重新思考 Go 系列:这个系列希望结合工作中在 Go 编程与性能优化中遇到过的问题,探讨 Go 在语言哲学、底层实现和现实需求三者之间关系与矛盾。


Go 语言是一门为实现 CSP 并发模型而设计的语言,这也是它区别于其他语言最大的特色。而为了实现这一点,Go 在语法上就内置了 chan 的数据结构来作为不同协程间通信的载体。

Go 的 channel 提供 input 和 output 两种操作语法。input 一个已经 full 的 channel ,或是 output 一个 empty 的 channel 都会引发整个协程的阻塞。这个协程阻塞性能代价很低,也是协程让渡执行权的主要方法。

ch := make(chan int, 1024)
// input
ch <- 1
// output
<-ch

然而 channel 的实现恰好和进程内消息队列的大部分需求是吻合的,所以这个结构时常被用来作为生产者消费者模型的实现,甚至还作为 channel 的主流应用场景而推广。

但事实上,如果真的把该数据结构用来作为系统内核心链路的生产消费者模型底层实现,一不留神就会遇到雪崩级别的问题,且这些问题都不是简单的代码修改便能解决的。

Input 失败导致阻塞

当 channel 满的时候,<- 操作会导致整个 goroutine 阻塞。显然这并不总是编程者希望的,所以 Go 提供了 select case 的方法来判断 <- 是否成功:

select {
case ch <- 1:
default:
 // input failed
}

但问题是,当 channel input 失败时,编程者还能怎么做?除非队列的消息是可以被丢弃的,否则我们可能只再去创建一个类似 queue 的结构,将这部分消息缓存下来。但是这个 queue 的结构可能又要和这个 ch 本身的队列顺序处理好并发关系。