Golang协程使用中遇到的意外输出问题

Golang协程使用中遇到的意外输出问题

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func main() {
	wg.Add(2)

	go func() {
		fmt.Println("Hello form one")
		wg.Done()
	}()

	go func() {
		fmt.Println("Hello form two")
		wg.Done()
	}()

	wg.Wait()
}

运行上述程序。 为什么先打印出“Hello form two”,然后才是“Hello from one”。


更多关于Golang协程使用中遇到的意外输出问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

最后一个 go 位于下一个预定位置。

更多关于Golang协程使用中遇到的意外输出问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go 编程语言规范

Go 语句

“go” 语句启动一个函数调用,使其作为一个独立的并发控制线程(或称 goroutine)在同一个地址空间内执行。

Go 运行时调度器以伪随机、公平的方式并发地运行 goroutine。

既然你无法预测一个公平骰子的投掷结果,你就必须尝试"操纵骰子"。例如:

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var wg sync.WaitGroup

func main() {
	runtime.GOMAXPROCS(1)

	wg.Add(2)

	go func() {
		fmt.Println("Hello form one")
		wg.Done()
	}()

	runtime.Gosched()

	go func() {
		fmt.Println("Hello form two")
		wg.Done()
	}()

	wg.Wait()
}

Go Playground - The Go Programming Language

Hello form one
Hello form two

这是一个典型的并发执行顺序问题。在Go语言中,goroutine的调度是非确定性的,执行顺序由Go运行时调度器决定。

核心原因:

  • 两个goroutine是并发执行的,没有同步机制保证执行顺序
  • Go调度器根据系统状态决定哪个goroutine先获得CPU时间片
  • 每次运行都可能得到不同的输出顺序

示例代码证明:

package main

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

var wg sync.WaitGroup

func main() {
	// 运行多次观察不同结果
	for i := 0; i < 5; i++ {
		wg.Add(2)
		
		go func(id int) {
			fmt.Printf("Iteration %d: Hello from goroutine %d\n", i, id)
			wg.Done()
		}(1)
		
		go func(id int) {
			fmt.Printf("Iteration %d: Hello from goroutine %d\n", i, id)
			wg.Done()
		}(2)
		
		wg.Wait()
		fmt.Println("---")
		time.Sleep(time.Millisecond * 10) // 增加调度变化
	}
}

输出可能类似:

Iteration 5: Hello from goroutine 2
Iteration 5: Hello from goroutine 1
---
Iteration 5: Hello from goroutine 1
Iteration 5: Hello from goroutine 2
---
Iteration 5: Hello from goroutine 2
Iteration 5: Hello from goroutine 1
---

如果需要固定顺序,必须使用同步机制:

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func main() {
	wg.Add(2)
	
	ch := make(chan struct{}) // 同步通道
	
	go func() {
		<-ch // 等待信号
		fmt.Println("Hello form one")
		wg.Done()
	}()
	
	go func() {
		fmt.Println("Hello form two")
		close(ch) // 发送信号
		wg.Done()
	}()
	
	wg.Wait()
}

在这个修改版本中,第二个goroutine会先执行,第一个goroutine会等待通道信号,从而保证"Hello form two"总是先打印。

回到顶部