试图了解goroutines

时间:2016-04-17 14:11:02

标签: go goroutine

我一直在使用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

没有问好。它有两个goroutines甚至更奇怪

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

现在根本没有输出。当我将i < 5更改为i < 2并致电

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

我得到了

world
hello
hello

我在这里缺少什么?

4 个答案:

答案 0 :(得分:5)

的情况下
 say("world")
 go say("hello")

“世界”调用必须在“hello”goroutine启动之前完成。 “hello”goroutine无法运行或完成because main returns

对于

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

由于主要回归,goroutines不会运行或完成。

使用sync.WaitGroup阻止main在goroutines完成之前退出:

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

答案 1 :(得分:2)

祝贺学习Go。作为一个新人,很高兴理解并发性以及它与并行性的不同之处。

<强>并发
并发就像一个玩杂耍的人,用一只手在空中玩弄几个球。无论他玩杂耍多少球,任何时候只有一个球碰到他的手。

<强>并行
当变戏法者用另一只手并行开始玩弄更多球时,我们同时运行两个并发进程

Goroutines很棒,因为他们同时并发自动并行,具体取决于可用的计算核心和正在设置的GOMAXPROCS变量。

单手变戏法者
回到单手,单核,并发的变戏法者。想象一下,他玩弄三个名为&#34; hello&#34;,&#34; world&#34;和&#34; mars&#34;分别将手作为main例程。

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时,会添加一个&#34;积压计数器&#34;使用WaitGroup.Add(1)并致电WaitGroup.Done()以减少计数器。一旦积压变为0,这意味着所有goroutine都已完成,WaitGroup应该停止等待(并抓住球!)。

虽然使用频道进行同步很合适,但我们鼓励使用适当的并发工具,特别是当使用频道使代码更复杂且难以理解时。

答案 2 :(得分:1)

这是因为主要功能已经退出。

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

您添加了一项声明:

time.Sleep(100 * time.Second)

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

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

答案 3 :(得分:0)

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

你正在创建两个独立的goroutine,一个是goroutine的主要功能,一个是go say(&#34; world&#34;)。通常,当执行函数时,程序跳转到该函数,执行内部的所有代码,然后跳转到调用函数的行之后。

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

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