Golang中为什么下层的goroutine会先执行?

Golang中为什么下层的goroutine会先执行?

package main

import (
	"fmt"
	"sync"
	"time"
)

func odd(ch chan int, wg *sync.WaitGroup) {
	for i := 1; ; i += 2 {
		<-ch
		fmt.Println(i)
		time.Sleep(time.Second)
		ch <- 1
	}
	wg.Done()
}

func even(ch chan int, wg *sync.WaitGroup) {
	for i := 2; ; i += 2 {
		<-ch
		fmt.Println(i)
		time.Sleep(time.Second)
		ch <- 1
	}
	wg.Done()
}

func main() {
	ch := make(chan int)
	var wg sync.WaitGroup
	wg.Add(2)
	go even(ch, &wg)
	go odd(ch, &wg)
	ch <- 1
	wg.Wait()
}

我想知道为什么名为 odd 的函数会先执行,然后才是名为 even 的函数?我是不是遗漏了一些非常基础的东西?


更多关于Golang中为什么下层的goroutine会先执行?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

对!天气 😄

更多关于Golang中为什么下层的goroutine会先执行?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


两个 Go 协程之间没有确定的执行顺序。

观察到的结果可能会因编译所用的 Go 版本、CPU/核心数量、环境条件以及随机性而改变。

telo_tade:

@NobbZ & @aceix 请不要再抨击原帖作者了,即使他的问题表述不佳,但也是合理的。

我并没有抨击。我总是用 weathersolar flares 来夸张地比喻那些我们完全无法控制的、非确定性的随机因素。如果这被理解为“抨击”,我为此感到抱歉,这真的不是我的本意。

MukulLatiyan:

package main

import (
	"fmt"
	"sync"
	"time"
)

func odd(ch chan int, wg *sync.WaitGroup) {
	for i := 1; i < 5; i += 2 {
		<-ch
		fmt.Println("odd", i)
		time.Sleep(time.Second)
		ch <- 1
	}
	wg.Done()
}

func even(ch chan int, wg *sync.WaitGroup) {

	for i := 2; i < 5; i += 2 {
		<-ch
		fmt.Println("even", i)
		time.Sleep(time.Second)
		ch <- 1
	}
	wg.Done()
}

func main() {
	ch := make(chan int)
	var wg sync.WaitGroup
	wg.Add(2)

	go even(ch, &wg)
	go odd(ch, &wg)
	ch <- 1

	time.Sleep(3 * time.Second)
	<-ch

	wg.Wait()
}

看看这个稍微修改过的程序版本。它的优点是这段代码运行后会结束。我在 Go Playground 上运行了 10 次,其中有一次它先打印了 “even 2”,其他情况下都是 “odd 2”。

决定哪个 goroutine 先运行的因素,是这两个 goroutine 在运行时的调度方式。第一个 goroutine 比第二个有微小的优势,因为主函数先启动它,然后才启动第二个。

一旦两个 goroutine 中的任何一个开始运行,由于它会向通道写入数据,而另一个 goroutine 在等待从同一个通道读取数据,所以顺序就被固定下来了。

请不要再抨击原帖作者了,他的问题即使表述不佳,也是合理的。

在Golang中,goroutine的执行顺序是不确定的,由调度器决定。但在你的代码中,odd函数先执行的原因是通道通信的同步机制goroutine启动的时序共同作用的结果。

具体分析如下:

  1. 通道的同步特性:无缓冲通道ch的发送和接收操作会阻塞,直到另一端准备好。当ch <- 1执行时,会等待有goroutine执行<-ch来接收数据。

  2. goroutine启动顺序:虽然go even(ch, &wg)先被调用,但goroutine的启动需要时间。实际执行时,可能是odd<-ch先准备好接收数据。

  3. 代码执行流程

    func main() {
        ch := make(chan int)      // 创建无缓冲通道
        var wg sync.WaitGroup
        wg.Add(2)
        
        go even(ch, &wg)          // 启动even goroutine(可能还未开始执行循环)
        go odd(ch, &wg)           // 启动odd goroutine(可能还未开始执行循环)
        
        ch <- 1                   // 主goroutine发送数据,阻塞等待接收者
        
        // 此时哪个goroutine先执行到 <-ch,哪个就先接收数据
        wg.Wait()
    }
    
  4. 实际执行示例

    // 添加调试信息来观察执行顺序
    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func odd(ch chan int, wg *sync.WaitGroup, id string) {
        fmt.Printf("%s: started\n", id)
        for i := 1; ; i += 2 {
            fmt.Printf("%s: waiting to receive\n", id)
            <-ch
            fmt.Printf("%s: received, printing %d\n", id, i)
            time.Sleep(time.Second)
            ch <- 1
        }
        wg.Done()
    }
    
    func main() {
        ch := make(chan int)
        var wg sync.WaitGroup
        wg.Add(2)
        
        go odd(ch, &wg, "odd")
        go odd(ch, &wg, "even")  // 使用相同函数便于观察
        
        time.Sleep(100 * time.Millisecond) // 给goroutine启动时间
        fmt.Println("main: sending initial value")
        ch <- 1
        
        wg.Wait()
    }
    
  5. 控制执行顺序的方法:如果需要特定的执行顺序,可以使用同步原语:

    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func main() {
        ch := make(chan int)
        var wg sync.WaitGroup
        var mu sync.Mutex
        
        mu.Lock() // 先锁住even
        
        wg.Add(2)
        
        go func() {
            mu.Lock() // even等待锁
            defer mu.Unlock()
            for i := 2; ; i += 2 {
                <-ch
                fmt.Println(i)
                time.Sleep(time.Second)
                ch <- 1
            }
            wg.Done()
        }()
        
        go func() {
            for i := 1; ; i += 2 {
                <-ch
                fmt.Println(i)
                time.Sleep(time.Second)
                ch <- 1
            }
            wg.Done()
        }()
        
        ch <- 1
        mu.Unlock() // 释放锁,even可以执行
        
        wg.Wait()
    }
    

在你的原始代码中,odd先执行是因为调度器让odd<-ch先于even<-ch执行。这种顺序不是固定的,多次运行可能会得到不同的结果。这是Go并发模型的设计特点:goroutine的执行顺序是非确定性的。

回到顶部