go协程类似一个线程,但是go协程是由go自己调度,而不是系统。在协程中的代码可以和其他代码并发执行。让我们看一个例子:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("start")
go process()
time.Sleep(time.Millisecond * 10) // this is bad, don't do this!
fmt.Println("done")
}
func process() {
fmt.Println("processing")
}
这个例子有一些有趣的事,但是最重要的是了解我们是如何启动一个go协程。我们只是简单的将go
关键字附在我们想要执行的函数前面即可。如果我们只想执行一小段代码,例如上面的例子一样,我们可以使用一个匿名函数。需要注意的是,匿名函数不只是在go协程中使用,其他地方也可以。
go func() {
fmt.Println("processing")
}()
go协程很容易创建且开销较小。最终多个go协程将会在同一个底层的系统线程上运行。这也常称之为M:N
线程模型,因为我们有M
个应用线程(go协程)运行在N
个系统线程上。结果就是,一个go协程的开销和系统线程比起来相对很低(一般都是几KB)。在现代的硬件上,有可能拥有成千上万个go协程。
另外,这里还隐藏了映射和调度的复杂性。我们只需要说这段代码需要并发执行,然后让go自己去处理。
如果我们回到刚刚的例子中,你将会注意到我们使用了Sleep
让程序等待了几毫秒。这是因为主进程在退出前协程才有机会去执行(主进程在退出前不会等待所有协程都执行完毕)。为了解决这个问题,我们必须让代码协同。