Goroutine 是由 Go 运行时管理的轻量级执行线程。启动新的 goroutine 时,在函数调用前使用关键字 go。在内部,会创建一个描述堆栈和任务状态的结构,该结构会被添加到 goroutine 调度程序的队列中。
与操作系统线程不同,goroutine 的初始堆栈要小得多(通常为 2 KB),并且堆栈会在必要时自动扩展。Go 调度程序会在可用的操作系统线程之间自行分配它们(M:N 模型)。
与线程(threads)的主要区别:
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d ", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d ", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { <-results } }
Go 的 goroutine 能否在多个核心上并行执行?
常常会出现错误的回答:“不,因为 Go 使用绿色线程。”实际上,通过环境变量或调用 runtime.GOMAXPROCS(n),Go 可以在所有可用的 CPU 核心上并行执行 goroutines。
import "runtime" func main() { runtime.GOMAXPROCS(4) // 允许使用 4 个核心 ... }
故事
在使用 Go 的 backend 服务项目中,通过 goroutines 实现了工作池,但程序员忘记限制同时运行的 goroutines 的数量。结果在负载增加时,应用程序启动了数千个 goroutines,导致内存耗尽和服务崩溃。问题通过设置活动 goroutines 的限制(例如,通过信号量或工作池)得以解决。
故事
一名员工在 goroutines 之间错误地同步数据,使用了普通的全局变量而没有使用互斥锁或通道。这导致了数据竞争(race condition),导致在处理支付时偶尔发生错误。问题直到在生产环境中运行后才被发现。
故事
在解析服务中,漏掉了在 select 中传递 nil 通道的时机:在关闭通道后,select 继续阻塞等待数据,部分 goroutines "挂起"。通过将 nil 分配给关闭的通道并正确处理 select 来修复。