Go 鲜为人知的角落|第 12 章 协程|《Darker Corners of Go》独译(已授权)

112 阅读2分钟

当前译本仍不稳定,如翻译有问题请及时联系 jacob953@csu.edu.cn

什么是 goroutine

通常情况下,goroutine 可以被认为是轻量级线程。goroutine 的启动非常快速,因为仅需要使用 2kb 内存就可以初始化堆栈(当然,也可以改动)。 goroutine 由 Go 的运行时系统管理(而不是操作系统),因此,在上下文切换的代价很低。 goroutine 就是为并发而生的,即使在多个硬件的线程上运行,它们也可以并行。

并发是指同时处理很多事情;并行是指同时做很多事情。

Rob Pike

goroutine 的效率是非常之高的,如果与 channel 相结合,它们很可能是 Go 的最佳功能。 尽管 goroutine 在 Go 中是无处不在的,但也存在一个极端但很好的例子。 当一个服务器管理大量并发的 websocket 连接时,goroutine 需要分别被单独管理,但更多可能是处于闲置状态的(不占用很多 CPU 或内存)。 为每个连接都创建一个线程,一旦连接数变得数以千计就会出现问题,然而,在使用 goroutine 时,产生数十万的连接也是可能的。

关于 goroutine 是如何工作的,可以 在这里 找到更详细的帖子。

运行 goroutine 并不能阻止程序退出

在 Go 中,当主函数退出时,程序也就退出了。此时,任何在后台运行的 goroutine 都会安静地停止。 下面的程序将退出,且不打印任何东西:

package main

import (
    "fmt"
    "time"
)

func goroutine1() {
    time.Sleep(time.Second)
    fmt.Println("goroutine1")
}

func goroutine2() {
    time.Sleep(time.Second)
    fmt.Println("goroutine2")
}

func main() {
    go goroutine1()
    go goroutine2()
}

为了确保这些 goroutine 可以完成,需要添加一些同步,例如使用通道或 sync.WaitGroup:

package main

import (
    "fmt"
    "sync"
    "time"
)

func goroutine1(wg *sync.WaitGroup) {
    time.Sleep(time.Second)
    fmt.Println("goroutine1")
    wg.Done()
}

func goroutine2(wg *sync.WaitGroup) {
    time.Sleep(time.Second)
    fmt.Println("goroutine2")
    wg.Done()
}

func main() {
    wg := &sync.WaitGroup{}
    wg.Add(2)
    go goroutine1(wg)
    go goroutine2(wg)
    wg.Wait()
}

输出:(该结果可能会颠倒)

goroutine2
goroutine1

goroutine 报警会使整个程序崩溃

在 goroutine 内部发生的报警时,必须用 defer 和 recover() 来处理。否则,整个应用程序将崩溃:

package main

import (
    "fmt"
    "time"
)

func goroutine1() {
    panic("something went wrong")
}

func main() {
    go goroutine1()
    time.Sleep(time.Second)
    fmt.Println("will never get here")
}
panic: something went wrong 

goroutine 18 [running]:
main.goroutine1()
        c:/projects/test/main.go:9 +0x45
created by main.main
        c:/projects/test/main.go:13 +0x45