【问题标题】:Trying to understand goroutines试图理解 goroutines
【发布时间】:2016-04-17 14:11:02
【问题描述】:

我一直在使用来自A Tour of Go 的以下代码,但是当我应用一些小改动时,我不明白发生了什么。原代码是这样的

package main

import (
    "fmt"
    "time"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

它会产生这个

world
hello
hello
world
world
hello
hello
world
world
hello

没关系:五次问候,五次世界。当我打电话时我开始变得奇怪

say("world")
go say("hello")

现在输出只是

world
world
world
world
world

没有你好。使用两个 goroutine 会更奇怪

go say("world")
go say("hello")

现在根本没有输出。当我将i &lt; 5 更改为i &lt; 2 并调用

go say("world")
say("hello")

我明白了

world
hello
hello

我在这里错过了什么?

【问题讨论】:

标签: go goroutine


【解决方案1】:

如果是

 say("world")
 go say("hello")

“world”调用必须在“hello”goroutine 启动之前完成。 “hello” goroutine 没有运行或完成 because main returns.

对于

go say("world")
go say("hello")

goroutines 没有运行或完成,因为 main 返回。

使用sync.WaitGroup 防止 main 在 goroutine 完成之前退出:

func say(wg *sync.WaitGroup, s string) {
  defer wg.Done()
  for i := 0; i < 5; i++ {
    time.Sleep(100 * time.Millisecond)
    fmt.Println(s)
  }
}

func main() {
  var wg sync.WaitGroup
  wg.Add(2)
  go say(&wg, "world")
  go say(&wg, "hello")
  wg.Wait()
}

playground example

【讨论】:

    【解决方案2】:

    恭喜你学习围棋。作为新手,很高兴了解并发以及它与并行的不同之处。

    并发
    并发就像一个杂耍者用一只手在空中玩几个球。不管他玩多少球,任何时候只有一个球碰到他的手。

    并行性
    当杂耍者开始用另一只手并行地杂耍更多球时,我们有两个并发的进程同时运行。

    Goroutines 很棒,因为它们既是并发又是自动并行,这取决于可用的计算核心和设置的GOMAXPROCS 变量。

    单手杂耍者
    回到单手、单核、并发的杂耍者。想象一下,他以main 的程序处理三个分别名为“hello”、“world”和“mars”的球。

    var balls = []string{"hello", "world", "mars"}
    
    func main() {
            go say(balls[0])
            go say(balls[1])
            go say(balls[2])
    }
    

    或者更恰当地说,

    func main() {
            for _, ball := range balls {
                    go say(ball)
            }
    }
    

    一旦三个球依次被抛向空中,杂耍者就会立即收回手。也就是说,main 例程在投出的第一个球甚至可以落在他的手上之前就退出了。可惜,球只是掉在地上。糟糕的表演。

    为了让球回到他的手中,杂耍者必须确保他等待他们。这意味着他的手需要能够跟踪和计算他扔的球,并了解每个球何时落地。

    最直接的方法是使用sync.WaitGroup

    import (
        "fmt"
        "time"
        "sync"
    )
    
    var balls = []string{"hello", "world", "mars"}
    var wg sync.WaitGroup
    
    func main() {
            for _, ball := range balls {
                    // One ball thrown
                    wg.Add(1)
                    go func(b string) {
                            // Signals the group that this routine is done.
                            defer wg.Done()
                            // each ball hovers for 1 second
                            time.Sleep(time.Duration(1) * time.Second)
                            fmt.Println(b)
                            // wg.Done() is called before goroutine exits
                    }(ball)
            }
    
            // The juggler can do whatever he pleases while the 
            // balls are hanging in the air.
    
            // This hand will come back to grab the balls after 1s.
            wg.Wait()
    }
    

    WaitGroup 很简单。当生成一个 goroutine 时,使用WaitGroup.Add(1) 添加一个“积压计数器”并调用WaitGroup.Done() 来减少计数器。一旦 backlog 变为 0,就意味着所有的 goroutine 都完成了,WaitGroup 应该停止等待(并抓住球!)。

    虽然使用通道进行同步很好,但建议酌情使用可用的并发工具,尤其是在使用通道使代码更复杂且难以理解时。

    【讨论】:

      【解决方案3】:

      这是因为main函数已经退出了。

      当 main 函数返回时,所有的 goroutine 都被突然终止,然后程序退出。

      你添加一个语句:

      time.Sleep(100 * time.Second)
      

      在主函数返回之前,一切顺利。

      但是在 Go 中一个好的做法是使用 channel,它用于 goroutine 之间的通信。您可以使用它让 main 函数等待后台 goroutine 完成。

      【讨论】:

        【解决方案4】:

        func main() { go say("world") say("hello") }

        你正在创建两个独立的 goroutine,一个是主函数 goroutine,一个是 go say("world")。通常在执行函数时,程序会跳转到该函数,执行里面的所有代码,然后跳转到调用函数的那一行。

        使用 goroutines 你不是在函数内部跳转,而是在一个单独的线程中启动 goroutine 并在调用之后继续执行该行而不等待它。

        因此,在主 goroutine 完成之前,goroutine 将没有时间完成。

        【讨论】:

          猜你喜欢
          • 2021-09-02
          • 2021-01-09
          • 2018-07-29
          • 2016-12-14
          • 1970-01-01
          • 2020-04-06
          • 2023-03-07
          • 2011-03-28
          • 2011-11-27
          相关资源
          最近更新 更多