Golang中使用"go"关键字和"runtime.Gosched()"时的控制流问题

Golang中使用"go"关键字和"runtime.Gosched()"时的控制流问题

package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
)

func main() {
    fmt.Println("NumCPU:", runtime.NumCPU())
    fmt.Println("NumGoroutine: First", runtime.NumGoroutine())

    var counter int64
    const gs = 100
    var wg sync.WaitGroup
    wg.Add(gs)

    for i := 0; i < gs; i++ {
        go func() {
            atomic.AddInt64(&counter, 1)
            runtime.Gosched()
            fmt.Println("Counter:\t", atomic.LoadInt64(&counter))
            wg.Done()
        }()
        fmt.Println("NumGoroutine:", runtime.NumGoroutine())
    }

    wg.Wait()
    fmt.Println("NumGoroutine: Last", runtime.NumGoroutine())
    fmt.Println("count:", counter)
}

大家好,我是Go语言的新手。在学习Go的并发时,我遇到了这段代码,但无法理解其控制流程。我知道由于所有内容都是并行运行的,所以输出结果永远不会相同。

以下是我在终端/命令行中得到的输出:

NumCPU: 4 NumGoroutine: First 1 NumGoroutine: 2 Counter: 1 NumGoroutine: 2 Counter: 2 NumGoroutine: 2 Counter: 3 NumGoroutine: 2 Counter: 4 NumGoroutine: 3 Counter: 5 NumGoroutine: 2 Counter: 6 NumGoroutine: 2 Counter: 7 NumGoroutine: 3 Counter: 8 NumGoroutine: 3 Counter: 9 NumGoroutine: 3 Counter: 10 NumGoroutine: 3 Counter: 11 NumGoroutine: 3 Counter: 12 NumGoroutine: 3 Counter: 13 NumGoroutine: 3 Counter: 14 NumGoroutine: 3 Counter: 15 NumGoroutine: 3 Counter: 16 NumGoroutine: 3 Counter: 17 NumGoroutine: 3 Counter: 18 NumGoroutine: 3 Counter: 19 NumGoroutine: 3 Counter: 20 NumGoroutine: 3 Counter: 21 NumGoroutine: 3 Counter: 22 NumGoroutine: 3 Counter: 23 NumGoroutine: 3 Counter: 24 NumGoroutine: 3 Counter: 25 NumGoroutine: 3 Counter: 26 NumGoroutine: 3 Counter: 27 NumGoroutine: 3 Counter: 28 NumGoroutine: 3 Counter: 29 NumGoroutine: 3 Counter: 30 NumGoroutine: 3 Counter: 31 NumGoroutine: 3 Counter: 32 NumGoroutine: 3 Counter: 33 NumGoroutine: 3 Counter: 34 NumGoroutine: 3 Counter: 35 NumGoroutine: 3 Counter: 36 NumGoroutine: 3 Counter: 37 NumGoroutine: 3 Counter: 38 NumGoroutine: 3 NumGoroutine: 3 Counter: 39 Counter: 40 NumGoroutine: 4 Counter: 41 NumGoroutine: 3 Counter: 42 NumGoroutine: 3 Counter: 43 NumGoroutine: 3 Counter: 44 NumGoroutine: 3 Counter: 45 NumGoroutine: 3 Counter: 46 NumGoroutine: 3 Counter: 47 NumGoroutine: 3 NumGoroutine: 3 Counter: 48 Counter: 49 NumGoroutine: 4 Counter: 50 NumGoroutine: 3 Counter: 51 NumGoroutine: 3 Counter: 52 NumGoroutine: 3 Counter: 53 NumGoroutine: 3 Counter: 54 NumGoroutine: 3 NumGoroutine: 3 Counter: 55 Counter: 56 NumGoroutine: 4 Counter: 57 NumGoroutine: 3 Counter: 58 NumGoroutine: 3 Counter: 59 NumGoroutine: 3 Counter: 60 NumGoroutine: 3 NumGoroutine: 3 Counter: 61 Counter: 62 NumGoroutine: 4 Counter: 63 NumGoroutine: 3 NumGoroutine: 3 Counter: 64 Counter: 65 NumGoroutine: 4 Counter: 66 NumGoroutine: 3 Counter: 67 NumGoroutine: 3 Counter: 68 NumGoroutine: 3 Counter: 69 NumGoroutine: 3 Counter: 70 NumGoroutine: 3 Counter: 71 NumGoroutine: 3 Counter: 72 NumGoroutine: 3 Counter: 73 NumGoroutine: 3 Counter: 74 NumGoroutine: 3 Counter: 75 NumGoroutine: 3 Counter: 76 NumGoroutine: 3 Counter: 77 NumGoroutine: 3 Counter: 78 NumGoroutine: 3 Counter: 79 NumGoroutine: 3 Counter: 80 NumGoroutine: 3 Counter: 81 NumGoroutine: 3 Counter: 82 NumGoroutine: 3 Counter: 83 NumGoroutine: 3 Counter: 84 NumGoroutine: 3 Counter: 85 NumGoroutine: 3 Counter: 86 NumGoroutine: 3 Counter: 87 NumGoroutine: 3 Counter: 88 NumGoroutine: 3 Counter: 89 NumGoroutine: 3 Counter: 90 NumGoroutine: 3 Counter: 91 NumGoroutine: 3 Counter: 92 NumGoroutine: 3 Counter: 93 NumGoroutine: 3 Counter: 94 NumGoroutine: 3 Counter: 95 NumGoroutine: 3 Counter: 96 NumGoroutine: 3 Counter: 97 NumGoroutine: 3 Counter: 98 NumGoroutine: 3 Counter: 99 NumGoroutine: 3 Counter: 100 NumGoroutine: Last 1 count: 100

当程序第一次执行到 runtime.Gosched() 这一行时会发生什么?控制流程会转向哪里?

为什么在输出的第三行,NumGoroutine 会在 count 之前打印出来?


更多关于Golang中使用"go"关键字和"runtime.Gosched()"时的控制流问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

感谢您付出的时间和努力 Jeff Emanuel。

更多关于Golang中使用"go"关键字和"runtime.Gosched()"时的控制流问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


为什么在输出的第三行,NumGoroutine 会先于 count 打印出来?

因为在打印 “NumGoroutine” 的那行代码执行之前,循环中的 goroutine 还没有开始执行。goroutine 是异步运行的。如果没有显式的同步机制,你无法控制它相对于其他 goroutine 的运行时机。

这段代码展示了Go调度器的工作机制。关键点在于runtime.Gosched()和goroutine调度之间的交互。

当程序执行到runtime.Gosched()时,当前goroutine会主动让出处理器,允许其他goroutine运行。控制流程会转向Go调度器,由调度器决定接下来运行哪个goroutine。

关于输出顺序的问题,这里有一个更清晰的示例来说明调度顺序:

package main

import (
    "fmt"
    "runtime"
    "sync"
    "sync/atomic"
)

func main() {
    fmt.Println("NumCPU:", runtime.NumCPU())
    fmt.Println("NumGoroutine: First", runtime.NumGoroutine())

    var counter int64
    const gs = 5  // 减少数量以便观察
    var wg sync.WaitGroup
    wg.Add(gs)

    for i := 0; i < gs; i++ {
        go func(id int) {
            fmt.Printf("Goroutine %d: before atomic.Add\n", id)
            atomic.AddInt64(&counter, 1)
            
            fmt.Printf("Goroutine %d: before Gosched\n", id)
            runtime.Gosched()
            
            fmt.Printf("Goroutine %d: Counter = %d\n", id, atomic.LoadInt64(&counter))
            wg.Done()
        }(i)
        
        fmt.Printf("Main: after launching goroutine %d, NumGoroutine = %d\n", 
            i, runtime.NumGoroutine())
    }

    wg.Wait()
    fmt.Println("NumGoroutine: Last", runtime.NumGoroutine())
    fmt.Println("count:", counter)
}

输出可能类似这样:

NumCPU: 4
NumGoroutine: First 1
Main: after launching goroutine 0, NumGoroutine = 2
Main: after launching goroutine 1, NumGoroutine = 3
Goroutine 0: before atomic.Add
Goroutine 0: before Gosched
Main: after launching goroutine 2, NumGoroutine = 4
Goroutine 1: before atomic.Add
Goroutine 1: before Gosched
Goroutine 0: Counter = 2
Main: after launching goroutine 3, NumGoroutine = 4
Goroutine 2: before atomic.Add
Goroutine 2: before Gosched
Goroutine 1: Counter = 3
Main: after launching goroutine 4, NumGoroutine = 5
Goroutine 3: before atomic.Add
Goroutine 3: before Gosched
Goroutine 2: Counter = 4
Goroutine 4: before atomic.Add
Goroutine 4: before Gosched
Goroutine 3: Counter = 5
Goroutine 4: Counter = 5
NumGoroutine: Last 1
count: 5

在你的原始代码中,NumGoroutinecount之前打印是因为:

  1. 主goroutine在循环中启动新的goroutine后立即执行fmt.Println("NumGoroutine:", runtime.NumGoroutine())
  2. 新启动的goroutine执行atomic.AddInt64(&counter, 1)然后调用runtime.Gosched()
  3. runtime.Gosched()让出处理器后,控制权可能返回到主goroutine,继续打印NumGoroutine
  4. 或者另一个goroutine获得处理器,打印Counter

这种交错输出是由于goroutine的并发执行和调度器的非确定性调度造成的。每次运行都可能产生不同的输出顺序,这正是并发编程的特点。

回到顶部