Golang中循环内使用Goroutines的实践与技巧

Golang中循环内使用Goroutines的实践与技巧 在这段代码中:https://play.golang.org/p/wPt5RwYy3ij

package main

import (
	"time"
)

func main() {
	for i := 0; i < 2; i++ {
		go func() {
			println(i)
		}()
	}
	time.Sleep(1)
}

输出:

2
2

为什么?

如果将 i 作为参数传递给协程函数,那么它可以正常工作。但我无法理解为什么 i 会取切片的最大长度值。

3 回复

看看这个:https://play.golang.org/p/eCtEpBUM4wc

基本上,goroutine 在循环退出之前不会执行,这就是为什么通常使用数据通道来推送值,然后由 goroutine 来消费。

查看这个通道实现的示例:https://play.golang.org/p/duV3JgU4yWJ

更多关于Golang中循环内使用Goroutines的实践与技巧的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你还应该查看这个:

GitHub

GitHub

头像

golang/go

go - Go 编程语言

在Go语言中,这个问题是由于闭包捕获变量的特性导致的。当在循环内创建goroutine并使用外部变量i时,所有goroutine都共享同一个i变量的引用,而不是在创建时捕获i的当前值。

问题分析

在你的代码中:

for i := 0; i < 2; i++ {
    go func() {
        println(i)  // 这里捕获的是i的引用,不是值
    }()
}

执行过程:

  1. 循环启动两个goroutine
  2. goroutine被调度执行时,循环可能已经结束
  3. 此时i的值已经变为2(循环终止条件)
  4. 两个goroutine都打印最终的i值:2

解决方案

方法1:传递参数(推荐)

package main

import (
    "time"
)

func main() {
    for i := 0; i < 2; i++ {
        go func(x int) {
            println(x)
        }(i)  // 传递i的当前值
    }
    time.Sleep(time.Millisecond)
}

方法2:在循环内创建局部变量

package main

import (
    "time"
)

func main() {
    for i := 0; i < 2; i++ {
        i := i  // 创建局部变量
        go func() {
            println(i)
        }()
    }
    time.Sleep(time.Millisecond)
}

方法3:使用WaitGroup等待所有goroutine完成

package main

import (
    "sync"
)

func main() {
    var wg sync.WaitGroup
    
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func(x int) {
            defer wg.Done()
            println(x)
        }(i)
    }
    
    wg.Wait()  // 等待所有goroutine完成
}

输出结果

使用上述任一方法,输出都会是:

0
1

关键点

  • goroutine的调度时机不确定
  • 闭包捕获的是变量的引用,不是创建时的值
  • 传递参数或创建局部变量可以解决这个问题
  • 在生产代码中应该使用sync.WaitGroup而不是time.Sleep
回到顶部