How do goroutines work?

huangapple go评论83阅读模式
英文:

How do goroutines work?

问题

我正在为你翻译以下内容:

我正在按照Go Tour的教程学习,但在使用goroutines时遇到了一些困难。我知道goroutines非常轻量级,每当一个goroutine被阻塞时,另一个goroutine就会开始执行,但我无法理解这个示例是如何工作的:

package main

import (
    "fmt"
    "time"
)

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

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

Playground

我知道一个goroutine会为带有参数"world"的say函数启动,但据我了解,它应该打印"world"五次和"hello"一次。然而,我不明白为什么输出结果是这样的:

hello
world
hello
world
hello
world
hello
world
hello

根据我对其他语言中线程的有限理解,输出结果应该是这样的:

hello
world
world
world
world
world

或者是这样的:

world 
world
world
hello
world
world

为什么第二行也执行了五次?在go语句下面的任何内容都被视为goroutine的一部分吗?

另外,下一张幻灯片展示了另一个我无法理解的例子:

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- sum // 将sum发送到c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c // 从c接收数据

    fmt.Println(x, y, x+y)
}

Playground

一个goroutine被启动来处理切片的后半部分,然后另一个goroutine被启动来处理切片的前半部分,然而变量xy被赋予了两个不同的值。在我看来,sum函数会将其求和结果发送到通道c,然后下一个sum函数会将其求和结果发送到同一个通道c,那么这两个变量为什么会被赋予两个不同的值?通道c不应该只有一个sum值吗?

我很感谢你提出这个相当长的问题,但我无法找到这些问题的答案。

英文:

I was following the Go Tour and I am a bit stuck when it comes to goroutines. I understand that they are very lightweight and that every time a goroutine blocks, another one will start but I can't get my head around how this example actually works:

package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

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

func main() {
    go say(&quot;world&quot;)
    say(&quot;hello&quot;)
}

<kbd>Playground</kbd>

I understand that a goroutine is started for the say function with the argument "world", but as far as I understand that should print "world" five times and "hello" once. However I don't understand why the output is as it is:

hello
world
hello
world
hello
world
hello
world
hello

From my limited understanding of threads from other languages the output should have been something like this:

hello
world
world
world
world
world

or like this:

world 
world
world
hello
world
world

Why does the second line execute five times as well? Does anything below a go statement classify as part of the go routine?

Also the next slide shows something I can't get my head round again:

package main

import &quot;fmt&quot;

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c &lt;- sum // send sum to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := &lt;-c, &lt;-c // receive from c

    fmt.Println(x, y, x+y)
}

<kbd>Playground</kbd>

A goroutine is started for the second half of the slice and then another one for the first part of the slice, however the values x and y have been assigned two different values. The way I see it the sum function will send it's sum to channel c and then the next sum will send it's sum to the same channel c so how can the two variables be assigned two different values? Shouldn't channel c have one single sum value in there?

I appreciate that this is quite a long question but I wasn't able to find the answer to these questions.

答案1

得分: 5

为什么第二行也执行了5次?

第二行在main()线程中每秒执行5次打印hello。但是同时,第一行go say("world")也会在一个单独的goroutine中每秒执行5次打印worldSleep函数确保每个goroutine都会暂停,让其他goroutine继续执行。

因此输出结果为:

hello
world
hello
world
hello
world
hello
world
hello

在我看来,sum函数会将其求和结果发送到通道c,然后下一个求和结果也会发送到同一个通道c,那么这两个变量如何被赋予不同的值呢?

因为每次发送操作都会阻塞,直到通道c被读取。由于有两次向c发送数据,你需要进行两次读取:

x, y := <-c, <-c // 从c中接收两次数据

在Golang规范的赋值部分中,允许进行元组赋值,条件是:

左边的操作数数量必须等于右边的表达式数量,每个表达式都必须是单值的,第n个表达式会被赋值给左边的第n个操作数。

英文:

> Why does the second line execute 5 times as well?

The second line will print hello every second 5 times in the main() thread.
But concurrently the first line go say(&quot;world&quot;) will also print world every seconds five times in a separate goroutine.
The Sleep ensure that each routine yields, allowing the other to resume.

Hence the output:

hello
world
hello
world
hello
world
hello
world
hello

> The way I see it the sum function will send it's sum to channel c and then the next sum will send it's sum to the same channel c so how can the two variables be assigned two different values?

Because each send will block on c until channel c is read.
Since there are two write to c, you need to read:

 x, y := &lt;-c, &lt;-c // receive from c twice.

The Assignement section of Golang Spec allows for a tuple assignment if:

> the number of operands on the left must equal the number of expressions on the right, each of which must be single-valued, and the nth expression on the right is assigned to the nth operand on the left.

答案2

得分: 1

对于第一个函数,你应该看到VonC提供的值的样式。hello打印5次的原因是因为say函数打印了5次。想象一下没有goroutine的程序。我认为它不能保证你会完美地交替得到helloworld,但我可能错了。

通道工作的原因是:

  1. Golang允许你进行多重赋值,就像VonC提到的那样。
  2. 通道会"清空",也就是说当你将c赋值给x时,它会移除传递到通道中的第一个和值,当你将c赋值给y时,它会传递第二个值(同样,我认为顺序不是一个保证,因为x可能有前半部分和值,y有后半部分和值,或者反过来)。

如果你将通道想象成一种队列,我认为会更容易理解。求和的goroutine将值推入队列,而赋值操作按顺序弹出这些值。

英文:

For the first function you should see values in the style VonC presented. The reason hello prints 5 times as well is because the function say prints things 5 times. Just imagine the program without the goroutine. I think it doesn't guarantee that you will get hello and world perfectly interspersed but I may be wrong.

The reason the channel works is:

  1. Golang let's you do multiple assignment as VonC mentions
  2. Channels empty out, i.e. when you assign c to x it removes the first sum that was passed into the channel and when it assigns c to y it passes in the second value (again I think the order is not a guarantee as x could have the first half and y the second half sum or vice-versa.

If you imagine channels as a sort of a queue I think it makes more sense. The summing goroutines push values onto the queue and assignments pop the values sequentially.

huangapple
  • 本文由 发表于 2014年8月28日 16:28:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/25544214.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定