Go最初是为了编写高性能的网络和并行程序而设计的,因此特别关注内置调度器(scheduler)及通过goroutine进行简单的并行管理。与大多数语言中由用户直接管理的操作系统线程不同,Go中的goroutine更轻量,可以在固定数量的系统线程上同时运行成千上万的goroutine。
问题: 直接管理线程(threads)比较复杂:这会导致资源快速耗尽、数据竞争和内存管理的困难。
解决方案: Go使用M:N模型——大量goroutine(M)在有限的操作系统线程(N)上进行多路复用。调度器在Go运行时的级别上实现,自动平衡和重新分配goroutine的执行。程序员只需管理goroutine的启动和同步,而不是直接管理操作系统线程。
代码示例:
package main import ( "fmt" "time" ) func worker(id int) { fmt.Printf("Worker %d starting ", id) time.Sleep(time.Second) fmt.Printf("Worker %d done ", id) } func main() { for i := 0; i < 5; i++ { go worker(i) } time.Sleep(2 * time.Second) }
关键特点:
每个goroutine会在单独的CPU内核上并发执行吗?
不。Goroutine是多路复用的,调度器确定实际并发执行的任务数量。
是否可以手动管理特定goroutine/线程的执行?
不。Go运行时不提供直接调度的接口。唯一的例外是GOMAXPROCS,用来设置操作系统的线程数。
大量goroutine是否意味着程序会自动加速?
不。大量的并发操作可能会导致额外的开销:上下文切换、资源争用、GC时间增加和内存消耗。
一个微服务并行处理传入请求,没有限制goroutine数量,并没有通过WaitGroup等待完成——结果是响应时间增加、数据竞争和不可预测的超时。
优点:
缺点:
通过goroutine池实现工人管理,同时工作任务的数量受到信号量或通道的限制。WaitGroup正确地等待所有任务的完成。
优点:
缺点: