【问题标题】:How does Golang share variables between goroutines?Golang 如何在 goroutine 之间共享变量?
【发布时间】:2017-01-05 13:33:29
【问题描述】:

我正在学习 Go 并试图了解它的并发特性。

我有以下程序。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)

        x := i

        go func() {
            defer wg.Done()
            fmt.Println(x)
        }()

    }

    wg.Wait()
    fmt.Println("Done")
}

执行时我得到:

4
0
1
3
2

这正是我想要的。但是,如果我对其稍作修改:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)

        go func() {
            defer wg.Done()
            fmt.Println(i)
        }()

    }

    wg.Wait()
    fmt.Println("Done")
}

我得到的是:

5
5
5
5
5

我不太明白其中的区别。谁能帮忙解释一下这里发生了什么以及 Go 运行时如何执行这段代码?

【问题讨论】:

标签: multithreading go concurrency goroutine


【解决方案1】:

一般规则是,不要在 goroutine 之间共享数据。在第一个示例中,您基本上为每个 goroutine 提供了自己的 x 副本,然后它们按照到达 print 语句的任何顺序将其打印出来。在第二个示例中,它们都引用了相同的循环变量,并且在它们中的任何一个打印它时递增到 5。我不相信那里的输出是有保证的,只是创建 goroutine 的循环完成的速度比 goroutine 本身到达打印部分的速度要快。

【讨论】:

    【解决方案2】:

    x := i 的每次运行都有新变量,
    这段代码很好地展示了差异,通过在 goroutine 中打印x 的地址:
    The Go Playground:

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            x := i
            go func() {
                defer wg.Done()
                fmt.Println(&x)
            }()
        }
        wg.Wait()
        fmt.Println("Done")
    }
    

    输出:

    0xc0420301e0
    0xc042030200
    0xc0420301e8
    0xc0420301f0
    0xc0420301f8
    Done
    

    然后使用 go build -race 构建您的第二个示例并运行它:
    你会看到:WARNING: DATA RACE


    这会很好The Go Playground

    //go build -race
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func main() {
        var wg sync.WaitGroup
        for i := 0; i < 5; i++ {
            wg.Add(1)
            go func(i int) {
                defer wg.Done()
                fmt.Println(i)
            }(i)
        }
        wg.Wait()
        fmt.Println("Done")
    }
    

    输出:

    0
    4
    1
    2
    3
    Done
    

    【讨论】:

    • 谢谢。这对我理解背后的逻辑很有帮助。
    • 最后一个例子是否正常,因为在启动/调用协程函数时变量i被复制?
    【解决方案3】:

    用简单的英语解释有点困难,但我会尽力而为。

    你看,每次你生成一个新的 goroutine 时,都有一个初始化时间,不管它多么微不足道,它总是在那里。所以,在你的第二种情况下,整个循环在任何 goroutine 开始之前就已经完成了变量的 5 次递增。当 goroutine 完成初始化后,他们看到的只是最终的变量值 5。

    在你的第一种情况下,x 变量保留了 i 变量的副本,以便当 goroutine 启动时,x get 传递给它们。请记住,这里增加的是i,而不是xx 已修复。所以,当 goroutines 启动时,它们会得到一个固定的值。

    【讨论】:

      猜你喜欢
      • 2016-09-19
      • 1970-01-01
      • 1970-01-01
      • 2021-01-27
      • 1970-01-01
      • 1970-01-01
      • 2019-05-01
      • 1970-01-01
      相关资源
      最近更新 更多