背景
根据一个 Goroutine 是否直接依赖用户交互,我们可以将 Goroutine 分为两大类,一类是直接依赖用户交互的前台协程,比如 HTTP Server Handler等;另一类是不直接依赖用户交互的后台协程,比如 HTTP Server,定时任务协程等。前台协程随用户的交互开始执行,随交互结束而结束,比较容易设计。本文主要讨论后台协程设计的一些通用套路。
一个良好的后台协程需要至少满足以下两个诉求:
- 容易控制,尤其是启动、停止、重启等操作。
- 状态容易被观察,比如是否正在运行中。
针对这两个诉求,我们来寻找一个通用的实现套路。
设计与实现
简陋的后台协程
得益于 Go 从语法上对并发的支持,写一个简陋的后台协程再简单不过了。我们从下面这个 Demo 开始讨论,这个 Demo 的任务很简单,每隔一秒钟将下一个斐波那契数输出在标准输出里面。
package main
type Fibonacci struct {
a, b int
}
func NewFibonacci() *Fibonacci {
return &Fibonacci{
a:0, b:1}
}
func (f *Fibonacci) Run() {
go func() {
for {
time.Sleep(time.Second)
fmt.Println(f.b)
f.a, f.b = f.b, f.a + f.b
}
}()
}
func main() {
NewFibonacci().Run()
}
直接执行这个程序,什么都不会输出,因为主协程里面没有任何逻辑执行,程序启动后直接就退出了,对吧?不过现实中许多后台协程就是这样写的,因为真实世界里很多主协程是有其它任务在执行的,所以 Fibonacci 会一直执行下去,直到程序结束。
入门级的后台协程
观察上面这个 Fibonacci 我们会发现它的一些缺陷:首先我们没法终止它,一旦启动就失控了;其次我们也没法观察它,比如在任何时候去向它要一个当前时间的斐波那契数,是要不到的。
先说控制,我们很容易想到一种方式,就是使用一个bool变量去维护协程是否需要继续运行下去。
然后获取斐波那契数这个事情也很简单,加一个方法就好了。
实际上,这种方案就是我遇到的大多数协程的实现方式。我们在 Fibonacci 上按这个方案写,代码就是这样:
type Fibonacci struct {
a, b int
stop bool
mtx sync.Mutex
}
func NewFibonacci() *Fibonacci {
return &Fibonacci{
a:0, b:1}
}
func (f *Fibonacci) Run() {
go func() {
for {
if f.isStop() {
break
}
time.Sleep(time.Second)
f.mtx.Lock()
fmt