【问题标题】:Golang channels, order of executionGolang 通道,执行顺序
【发布时间】:2019-01-30 11:46:47
【问题描述】:

我正在学习Go,并且已经运行过以下代码sn-p:

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, 2)
    go sum(a[0:3], c)
    go sum(a[3:6], c)
    x := <-c
    y := <-c
    // x, y := <-c, <-c // receive from c

    fmt.Println(x, y)
}

Output:

-5 17

Program exited.

谁能告诉我为什么“sum”函数的第二次调用是在第一次调用之前通过通道来的?在我看来,输出应该是:

17 -5

我还使用无缓冲通道对此进行了测试,它也提供了相同的输出顺序。我错过了什么?

【问题讨论】:

标签: go channel


【解决方案1】:

您正在代码中调用 go 例程,但您不知道该例程何时结束以及值将传递到缓冲通道。

由于这段代码是异步的,所以只要例程完成,它就会将数据写入通道并在另一端读取。在上面的示例中,您只调用了两个 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, 2, 4, 2, 8, 2, 7, 2, 99, -32, 2, 12, 32, 44, 11, 63}

    c := make(chan int)
    for i := 0; i < len(a); i = i + 5 {
        go sum(a[i:i+5], c)
    }
    output := make([]int, 5)
    for i := 0; i < 4; i++ {
        output[i] = <-c
    }
    close(c)

    fmt.Println(output)
}

此代码在不同示例运行中的输出是

[12 18 0 78 162] 

[162 78 12 0 18]

[12 18 78 162 0]

这是因为 goroutine 将输出异步写入缓冲通道。

希望这会有所帮助。

【讨论】:

  • 谢谢。这帮助很大。我至少现在看到,不能保证线程按照它们启动的顺序完成。但是,在我看来,至少第一个开始应该在第二个之前完成,至少在某些时候。我只有在我的代码 sn-p 中的 to go 语句之间引入 time.Sleep(1) 时才发生这种情况。
【解决方案2】:

Goroutines 是异步启动的,它们可以以任何顺序写入通道。如果您稍微修改一下示例,则更容易查看:

package main

import (
    "fmt"
    "time"
)

func sum(a []int, c chan int, name string, sleep int) {
    fmt.Printf("started goroutine: %s\n", name)
    time.Sleep(time.Second * time.Duration(sleep))

    sum := 0

    for _, v := range a { 
        sum += v
    }   
    fmt.Printf("about end goroutine: %s\n", name)
    c <- sum // send sum to c
}

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

    c := make(chan int, 2)
    go sum(a[0:3], c, "A", 1)
    go sum(a[3:6], c, "B", 1)
    x := <-c 
    y := <-c 
    // x, y := <-c, <-c // receive from c

    fmt.Println(x, y)
}

https://play.golang.org/p/dK4DT0iUfzY

结果:

started goroutine: B
started goroutine: A
about end goroutine: A
about end goroutine: B
17 -5

【讨论】:

  • 谢谢。这有帮助。你的答案是完全正确的。我接受了哈姆扎的回答,可能是因为我第二次读了它,直到我也读了他的,灯才亮起来。但我也赞成你的。我实际上问错了问题。真正的问题是为什么第二个线程在第一个线程之前完成。似乎第一个至少应该比第二个早完成 1/2 以上的时间。我不明白为什么 Hamza 的 sn-p 是随机的,而我的不是。但是,很明显,在涉及无缓冲通道的情况下,我不能指望任何特定的顺序。
【解决方案3】:

运行 golang.org 沙箱时,我每次都得到相同的结果。如上所述。但是当我在自己的沙箱(在我的计算机上)中运行相同的 sn-p 时,它有时会改变线程的顺序。这更令人满意。它表明我不能期望任何特定的线程执行顺序,这很直观。我只是想不通为什么我得到相同的执行顺序,这与线程启动的顺序相反。我认为这只是在 golang.org 的沙盒上抽签的运气。

【讨论】:

  • 每次在沙箱中得到相同结果的原因是程序的结果被缓存了。
  • 啊,有道理。我现在非常清楚这个问题。我看到的东西似乎是确定性的,但似乎不是。我也在自己的电脑上运行了这个程序。只要我以某种方式弄脏了文件,结果就是随机的,即使对于我第一篇文章中的原始 sn-p 也是如此
猜你喜欢
  • 2018-11-12
  • 2013-05-28
  • 2021-06-05
  • 2016-04-25
  • 2017-01-03
  • 2022-01-16
  • 1970-01-01
  • 2016-02-01
相关资源
最近更新 更多