You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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
}

做两组对照测试:

  1. 在main函数最开头加runtime.GOMAXPROCS(1),强制所有goroutine只能在1个核心上跑,记录总运行时间
  2. 把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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 00:27:03