在 Go 语言的并发编程中,我们经常会遇到这样的需求:后台处理一批数据或事件,并且希望在需要时能够优雅地终止处理过程。比如日志订阅、事件监听、数据流推送等场景。
本文以 go-ethereum 的事件订阅为例,介绍一种常见的“可控订阅/优雅退出”模式。
以 go-ethereum 的合约事件过滤为例,核心代码如下:
sub := event.NewSubscription(func(quit <-chan struct{}) error {
for _, log := range buff {
select {
case logs <- log:
case <-quit:
return nil
}
}
return nil
})
func(quit <-chan struct{}) error 这个函数签名的关键在于 quit <-chan struct{}。
它是一个只读 channel,外部可以通过关闭它来通知内部“你可以退出了”。
遍历数据(如日志切片 buff),每次通过 select:
event.NewSubscription 会返回一个 Subscription 对象,外部可以通过调用它的 Unsubscribe() 方法来关闭 quit channel,从而通知内部 goroutine 退出。
- 防止 goroutine 泄漏:如果没有退出机制,后台 goroutine 可能永远阻塞,造成资源泄漏。
- 响应式退出:可以随时响应外部的“取消”请求,提升系统健壮性。
- 通用性强:这种模式适用于任何需要“后台推送+可控退出”的场景。
- 只用关闭 quit channel,不要 close 业务数据 channel,避免多处 close 导致 panic。
- select 里必须有 <-quit 分支,否则无法响应退出。
- 适合用于:事件订阅、日志推送、流式数据处理等场景。
Go 的 channel 和 goroutine 天生适合做“可控订阅/优雅退出”。
只要记住:用 quit channel 传递退出信号,select 里监听它,外部通过关闭 quit channel 控制退出,就能写出健壮的后台处理逻辑。