编程后端开发者

解释一下在 Go 中什么是 goroutine。它是如何在内部工作的,以及与其他语言中的线程有什么不同?

用 Hintsage AI 助手通过面试

答案

Goroutine 是由 Go 运行时管理的轻量级执行线程。启动新的 goroutine 时,在函数调用前使用关键字 go。在内部,会创建一个描述堆栈和任务状态的结构,该结构会被添加到 goroutine 调度程序的队列中。

与操作系统线程不同,goroutine 的初始堆栈要小得多(通常为 2 KB),并且堆栈会在必要时自动扩展。Go 调度程序会在可用的操作系统线程之间自行分配它们(M:N 模型)。

与线程(threads)的主要区别:

  • 创建速度更快,占用内存更少
  • 由 Go 运行时调度,而不是操作系统
  • 根据核心自动扩展
  • 通过通道实现同步,防止多类竞争问题

使用 goroutines 和通道的示例:

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 来修复。