如何在Go/Golang中正确演示并发(concurrency)与并行(parallelism)
问题解答
当前程序无法判断是否并行的原因
你代码里所有的耗时等待逻辑都用time.Sleep实现,处于sleep状态的goroutine会被Go调度器直接挂起,不占用实际CPU计算资源。这种场景下哪怕CPU只有单核,调度器也能在多个goroutine的sleep间隙切换执行,最终输出的时间效果和并行没有差异,所以你没法从现有运行结果判断是否真的并行。
goroutine和内核线程的关系
goroutine是Go实现的用户态轻量协程,不会直接绑定独立的内核线程运行,它的调度由Go runtime的GMP调度模型负责:
- M:对应操作系统的内核线程
- P:逻辑处理器,持有goroutine运行需要的资源,默认数量等于设备的CPU逻辑核心数
- G:即我们编写的goroutine
只有当G绑定到P之后,才能放到M上实际运行。同一时间点一个P只能跑一个G,所以如果你的P数量大于1,不同的G就可以被调度到不同的M(不同的内核线程)上,在不同CPU核心同时执行,也就是并行运行。
你可以通过runtime.GOMAXPROCS(n)函数主动修改P的数量,控制程序并行的最大上限。
修改代码验证并行效果
把代码里的s_sleep替换成CPU密集型计算逻辑,完全不使用sleep,就能直观看到并行的差异:
// 替换原来的s_sleep,改为占满CPU的计算逻辑 func cpuRun(durationMs int) { start := time.Now() sum := 0 for time.Since(start) < time.Duration(durationMs)*time.Millisecond { sum++ } // 避免编译器优化掉无意义的sum变量 _ = sum }
做两组对照测试:
- 在main函数最开头加
runtime.GOMAXPROCS(1),强制所有goroutine只能在1个核心上跑,记录总运行时间 - 把GOMAXPROCS改成你CPU的逻辑核心数(比如4、8),再运行一次记录总时间
如果第二次的总运行时间远小于第一次,就说明goroutine确实在并行执行。
代码小提示
你当前的WaitGroup使用逻辑是正确的,不过嵌套启动goroutine的写法如果是给新手演示,建议补充注释说明每个wg.Done()的调用位置,避免其他人误以为是在第一层goroutine调用Done,出现等待逻辑错误。
演示完整代码
package main import ( "fmt" "sync" "time" ) //take order func takeOrder1(wg *sync.WaitGroup) { s_sleep(1000) fmt.Println("\nTaking order...", t_time()) go takeOrder2(wg) } func takeOrder2(wg *sync.WaitGroup) { s_sleep(1500) fmt.Println("\nOrder tooken!", t_time()) wg.Done() } //make fires func makeFries1(wg *sync.WaitGroup) { s_sleep(1500) fmt.Println("\nFrying fries...", t_time()) go makeFries2(wg) } func makeFries2(wg *sync.WaitGroup) { s_sleep(3000) fmt.Println("\nFries Fried!", t_time()) wg.Done() } //burn burger func makeBurger1(wg *sync.WaitGroup) { s_sleep(2000) fmt.Println("\nFlipping burger...", t_time()) go makeBurger2(wg) } func makeBurger2(wg *sync.WaitGroup) { s_sleep(5000) fmt.Println("\nCooked a burger!", t_time()) wg.Done() } //cook drink func pourDrink1(wg *sync.WaitGroup) { s_sleep(1000) fmt.Println("\nPutting ice in cup...", t_time()) go pourDrink2(wg) } func pourDrink2(wg *sync.WaitGroup) { s_sleep(3000) fmt.Println("\nPouring soda in cup...", t_time()) go pourDrink3(wg) } func pourDrink3(wg *sync.WaitGroup) { s_sleep(2500) fmt.Println("\nDrink poured!", t_time()) wg.Done() } //wipe table func cleanTable1(wg *sync.WaitGroup) { s_sleep(1000) fmt.Println("\n'Cleaning' table....", t_time()) go cleanTable2(wg) } func cleanTable2(wg *sync.WaitGroup) { s_sleep(1500) fmt.Println("\nTable 'clean'!", t_time()) wg.Done() } //delay func s_sleep(x int) { time.Sleep(time.Duration(x) * time.Millisecond) } //just to print time func t_time() string { return time.Now().Format("15:04:05") } //create array of tasks to complete var McDolansTasks = []func(*sync.WaitGroup){ takeOrder1, makeFries1, makeBurger1, pourDrink1, cleanTable1} //main function func main() { var waitGroup sync.WaitGroup // Set number of effective goroutines we want to wait upon waitGroup.Add(len(McDolansTasks)) for _, task := range McDolansTasks { // Pass reference to WaitGroup instance // Each of the tasks should call on WaitGroup.Done() go task(&waitGroup) } // Wait until all goroutines have completed execution. waitGroup.Wait() println("\nClock out for the day!") }
内容的提问来源于stack exchange,提问作者A. Salas
相关产品推荐
相关产品推荐

