【问题标题】:The Behaviour Of Goroutines with Channels带有通道的 Goroutines 的行为
【发布时间】:2019-06-21 13:25:33
【问题描述】:

下面给出的代码输出有点令人困惑,请帮助我理解通道和 goroutine 的行为以及如何 执行是否真的发生了。

我试图理解程序的流程但是“调用goroutine”之后的语句被执行,即使调用了goroutine, 稍后执行 goroutine 中的语句,

在第二次“调用 goroutine”时,行为不同,打印顺序/程序流程发生变化。

以下是代码:

    package main

    import "fmt"

    func main() {
        fmt.Println("1")
        done := make(chan string)
        go test(done)
        fmt.Println("7")
        fmt.Println(<-done)
        fmt.Println("8")
        fmt.Println(<-done)
        fmt.Println("9")
        fmt.Println(<-done)
    }
    func test(done chan string) {
        fmt.Println("2")
        done <- "3"
        done <- "10"
        fmt.Println("4")
        done <- "5"
       fmt.Println("6")
    }

以上代码的结果:

1
7
2
3
8
10
9
4
6
5

请帮助我了解为什么以及如何得出这个结果。

【问题讨论】:

  • 如果你继续运行它(而不是在操场上),你可能每次都会得到不同的结果。并发性是不确定的,您对非锁定操作顺序的任何期望都是错误的。在查看并发代码时,您必须以不同的方式考虑程序流程;它不是一个单一的、可预测的流,而是多个并发的独立流。它仅在它们相互影响的锁定点(例如通道发送/接收)。
  • 我已经在 VSCode 中运行了这段代码,但是每次的输出都是一样的,并且顺序没有改变。我知道并发是如何工作的,但是这个程序在 VSCode 中总是给出完全相同的输出,为什么会这样???
  • 因为它就是这样运行的。这里的顺序并不能完全保证,但是在相同条件下运行时也没有足够的变化来获得不同的输出。
  • 输出的哪一部分令人困惑。你期望什么价值?我建议您从一个较短的示例开始。
  • 当我运行一个包含 goroutines 的程序时,我知道在 goroutines 的情况下,输出总是会以不同的顺序排列,并且没有特定的顺序可以保证,但是我在 VSCode 中运行了这个程序 每次我运行它时它都会给出相同的输出,不管多少次。还有一件事,为什么程序在调用goroutine之后打印出语句,即为什么它打印出7 ,即使调用了go test (done) func/goroutine,为什么不呢?在调用 func/goroutine 时打印出 2 并且控制是使用 go test(done){} 函数???

标签: go channel goroutine channels


【解决方案1】:

欢迎来到 Stack Overflow。我希望我能帮助你更好地理解 channel 和 goroutines。

概念一:Channels

将通道可视化为管道,数据从一端进入另一端。输入的第一个数据是从另一侧出来的第一个数据。有缓冲通道和非缓冲通道,但对于您的示例,您只需要了解无缓冲的默认通道。无缓冲通道一次只允许一个值在通道中。

写入无缓冲通道

类似这样的代码将数据写入通道的一端。

ch <- value

现在,这段代码实际上 等待 执行完毕,直到有东西从通道中读取值。无缓冲通道一次只允许一个值在其中,并且在读取之前不会继续执行。稍后我们将看到这如何影响代码执行的顺序。

从无缓冲通道读取

要从无缓冲通道读取(可视化从通道中取出值),执行此操作的代码如下所示

[value :=] <-ch

当您阅读代码文档时,[things in] 方括号表示其中的内容是可选的。上面,如果没有 [value :=],您只会从通道中取出一个值,而不会将其用于任何事情。

现在当通道中有值时,这段代码有两个副作用。第一,它在我们现在所处的任何例程中从通道中读取值,然后继续处理该值。它的其他效果是允许将值放入通道的goroutine继续。这是理解示例程序所必需的关键部分。

如果通道中还没有值,它将等待一个值写入通道,然后再继续。换句话说,线程阻塞,直到通道有值要读取。

概念2:Goroutines

goroutine 允许您的代码同时继续执行两段代码。这可用于让您的代码执行得更快,或同时处理多个问题(想象一个服务器,其中多个用户同时从它加载页面)。

当您尝试找出在同时执行多个例程时执行代码的顺序时,就会出现您的问题。这是一个很好的问题,其他人已经正确地指出这取决于。当你生成两个 goroutine 时,执行哪些代码行的顺序是任意的。

下面带有 goroutine 的代码可能会首先打印 executing a()end main()。这是因为产生一个 gorouting 意味着有两个并发的执行流(线程)同时发生。在这种情况下,一个线程停留在main() 中,另一个开始执行a() 中的第一行。运行时如何决定先运行哪个是任意的。

func main() {
    fmt.Println("start main()")
    go a()
    fmt.Println("end main()")
}

func a() {
    fmt.Println("executing a()")
}

Goroutines + Channels

现在让我们使用通道来控制 get 执行的顺序,何时执行。 现在唯一的区别是我们创建了一个通道,将它传递给 goroutine,然后等待它的值被写入,然后再继续 main。从前面开始,我们讨论了从通道读取值的例程如何需要等到通道中有值才能继续。由于executing a() 总是在写入通道之前打印,我们将一直等待读取放入通道的值,直到打印executing a()。由于我们在打印end main() 之前从通道读取(发生在写入通道之后),所以executing a() 将始终在end main() 之前打印。我创建了this playground,所以你可以自己运行它。

func main() {
    fmt.Println("start main()")
    ch := make(chan int)
    go a(ch)
    <-ch
    fmt.Println("end main()")
}

func a(ch chan int) {
    fmt.Println("executing a()")
    ch <- 0
}

你的例子

我认为此时您可以弄清楚什么时候会发生什么,以及可能以不同的顺序发生什么。当我在脑海中经历它时,我自己的第一次尝试是错误的(参见编辑历史)。你必须要小心!在编辑时我不会给出正确的答案,因为我意识到这可能是一个家庭作业。

编辑:更多关于&lt;-done的语义

在我第一次浏览时,我忘了提到fmt.Println(&lt;-done) 在概念上与以下内容相同。

value := <-done
fmt.Println(value)

这很重要,因为它可以帮助您看到当main() 线程从done 通道读取时,它不会同时打印它。这是运行时的两个独立步骤。

我希望这会有所帮助。如果您有任何问题,请告诉我。

【讨论】:

  • 刚刚投了赞成票 - 并且想说,这简直是我在 Goroutines 频道上读到的最佳答案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多