8.1. Goroutines
在Go语言中,每一个并发的执行单元叫作一个goroutine。设想这里有一个程序有两个函数,一个函数做一些计算,另一个输出一些结果,假设两个函数没有相互之间的调用关系。一个线性的程序会先调用其中的一个函数,然后再调用来一个,但如果是在有两个甚至更多个goroutine的程序中,对两个函数的调用就可以在同一时间。我们马上就会看到这样的一个程序。
如果你使用过操作系统或者其它语言提供的线程,那么你可以简单地把goroutine类比作一个线程,这样你就可以写出一些正确的程序了。goroutine和线程的本质区别会在9.8节中讲。
当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。新的goroutine会用go语句来创建。在语法上,go语句是一个普通的函数或方法调用前加上关键字go。go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。
f() // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait
在下面的例子中,main goroutine会计算第45个菲波那契数。由于计算函数使用了效率非常低的递归,所以会运行相当可观的一段时间,在这期间我们想要让用户看到一个可见的标识来表明程序依然在正常运行,所以显示一个动画的小图标:
gopl.io/ch8/spinner
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n) // slow
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}
动画显示了几秒之后,fib(45)的调用成功地返回,并且打印结果: Fibonacci(45) = 1134903170
然后主函数返回。当主函数返回时,所有的goroutine都会直接打断,程序退出。除了从主函数退出或者直接退出程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是我们之后可以看到,可以通过goroutine之间的通信来让一个goroutine请求请求其它的goroutine,并让其自己结束执行。
注意这里的两个独立的单元是如何进行组合的,spinning和菲波那契的计算。每一个都是写在独立的函数中,但是每一个函数都会并发地执行。