【问题标题】:How do goroutines work?goroutines 是如何工作的?
【发布时间】:2014-10-22 01:04:59
【问题描述】:

我一直在关注Go Tour,但在 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 语句下方的任何内容是否属于 go 例程的一部分?

另外,下一张幻灯片显示了一些我无法再次理解的内容:

package main

import "fmt"

func sum(a []int, c chan int) {
    sum := 0
    for _, v := range a {
        sum += v
    }
    c <- 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 := <-c, <-c // receive from c

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

Playground

为切片的后半部分启动一个 goroutine,然后为切片的第一部分启动另一个 goroutine,但是值 xy 被分配了两个不同的值。在我看来,sum 函数会将其总和发送到通道 c,然后下一个 sum 会将其总和发送到同一通道 c 那么如何为这两个变量分配两个不同的值呢?频道c 不应该只有一个sum 值吗?

我很欣赏这是一个相当长的问题,但我无法找到这些问题的答案。

【问题讨论】:

    标签: concurrency go goroutine


    【解决方案1】:

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

    第二行将在main() 线程中每秒打印hello 5 次。
    同时第一行 go say("world") 还将在单独的 goroutine 中每秒钟打印世界五次。
    Sleep 确保每个例程都让步,允许另一个例程恢复。

    因此输出:

    hello
    world
    hello
    world
    hello
    world
    hello
    world
    hello
    

    在我看来,sum 函数会将其总和发送到通道 c,然后下一个总和会将其总和发送到同一个通道 c,那么如何为这两个变量分配两个不同的值?

    因为每次发送都会阻塞c,直到通道c被读取。
    由于c有两次写入,需要阅读:

     x, y := <-c, <-c // receive from c twice.
    

    Assignement section of Golang Spec 允许在以下情况下进行元组分配:

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

    【讨论】:

    • 哦。我想我明白了。正如你所说,main 本身就是一个 goroutine。 hello 被打印是因为 main 被 go 例程中的 sleep 阻塞了。因为每当一个例程被阻塞时,一个新的例程就会启动 main 重复 5 次,但同时该函数也重复自身 5 次。 Hello 首先被打印,因为代码跳过了等待它休眠的例程。
    • 这也是关于我的第二个例子的另一个问题。假设有数百万次调用 sum 函数,并在其前面加上 go。如果程序在总和完成计算之前没有到达阻塞程序的行,那么该值将丢失对吗?
    • @Bula 很抱歉迟到的答案。是的,main() 本身就是一个 goroutine,并且会在 Sleep() 上产生。
    • @Bula 不,值不会丢失:计算它的 goroutine 将在 c &lt;- sum 上阻塞,等待 main() 读取足够的值。
    • 如果我理解正确,每当c &lt;- sum 发生该程序将被阻止value &lt;- c 发生?
    【解决方案2】:

    对于第一个函数,您应该看到呈现的 VonC 样式的值。 hello 也打印 5 次的原因是因为函数 say 打印了 5 次。想象一下没有 goroutine 的程序。我认为这并不能保证你会得到完美穿插的helloworld,但我可能错了。

    频道工作的原因是:

    1. Golang 让你像 VonC 提到的那样做多项任务
    2. 通道empty out,即当您将c 分配给x 时,它会删除传递到通道中的第一个总和,而当它将c 分配给y 时,它会传递第二个值(再次我认为订单不是保证,因为x 可能有前半部分和y 后半部分总和,反之亦然。

    如果您将频道想象成一种队列,我认为这更有意义。求和 goroutine 将值推送到队列中,赋值顺序弹出值。

    【讨论】:

      最近更新 更多