runtime.GOMAXPROCS在显式创建更多协程时失效?如何正确使用runtime.GOMAXPROCS?
关于runtime.GOMAXPROCS的正确使用与并发协程控制误区
兄弟,你这是把GOMAXPROCS的作用和协程并发数控制搞混啦!咱先掰扯清楚这俩的区别,再看你代码里的问题出在哪。
先搞懂runtime.GOMAXPROCS到底管啥
runtime.GOMAXPROCS(n) 设置的是Go调度器能同时使用的操作系统线程数,不是你以为的“同时运行的协程数”。Go的协程(goroutine)是轻量级用户态线程,调度器会把大量协程多路复用到这些OS线程上。哪怕你设置GOMAXPROCS(2),照样能创建成千上万个协程——只是同一时刻最多有2个协程能在OS线程上执行,其他协程会处于等待调度的状态(比如阻塞、休眠或者就绪排队)。
你的代码为啥会启动5个协程?
看你代码里的这段:
var concurrency = 5 waiter := make(chan struct{}, concurrency) // ... for i := 0; i < items; i++ { wg.Add(1) waiter <- struct{}{} go worker(i, waiter, &wg) }
这里的waiter是缓冲为5的通道,意味着你可以连续往里面塞5个空结构体,然后一次性启动5个worker协程。这5个协程都会被创建并进入执行流程——只是因为GOMAXPROCS(2),同一时间最多2个能在CPU上跑,但剩下3个是就绪状态,一旦前面的协程让出CPU(比如time.Sleep的时候),调度器就会把它们调度到空闲的OS线程上运行。所以你看到的“5个协程在运行”,其实是指它们都已经启动了,只是不是同时占用CPU而已。
怎么实现“限制同时运行的协程数”?
如果你的需求是控制并发执行的协程数量(也就是并发度),那GOMAXPROCS不是干这个的,得用信号量模式(带缓冲的通道)来实现。给你改好的代码:
package main import ( "fmt" "runtime" "sync" "time" ) func worker(i int, sem chan struct{}, wg *sync.WaitGroup) { // 先获取信号量,没有可用的就阻塞等待 <-sem defer func() { fmt.Printf("worker %d done\n", i) wg.Done() // 协程结束后释放信号量,让下一个协程可以启动 sem <- struct{}{} }() fmt.Printf("worker %d starting\n", i) time.Sleep(time.Second) } func main() { runtime.GOMAXPROCS(2) // 这里设置你想要的并发度,比如2 const concurrency = 2 var items = 10 // 信号量通道的缓冲大小等于并发度 sem := make(chan struct{}, concurrency) // 先把信号量填满,初始化可用的“并发名额” for i := 0; i < concurrency; i++ { sem <- struct{}{} } var wg sync.WaitGroup for i := 0; i < items; i++ { wg.Add(1) go worker(i, sem, &wg) } wg.Wait() close(sem) }
修正点说明:
- 把原来的
waiter改成信号量sem,缓冲大小设为你想要的并发度(比如2) - worker协程启动时先获取信号量:
<-sem,如果信号量被占满,就会阻塞,直到有其他协程释放信号量 - 协程结束时释放信号量:
sem <- struct{}{},把“并发名额”让出来,让新的协程可以启动 - 这样就能保证同一时刻最多有
concurrency个协程在执行,配合GOMAXPROCS(2),就能达到你预期的“仅运行2个执行单元”的效果
最后总结下
GOMAXPROCS控制的是Go调度器使用的OS线程数,主要用来控制CPU核心的利用(比如你有4核CPU,设为4就能最大化利用CPU)- 要限制协程的并发执行数,必须用信号量模式(带缓冲的通道)来实现
- 你原代码的通道缓冲是5,相当于一次放了5个“启动名额”,所以会一次性启动5个协程,这和
GOMAXPROCS的设置不冲突——只是它们不会同时占用CPU而已
内容的提问来源于stack exchange,提问作者NonoPa Naka
相关产品推荐
相关产品推荐

