【问题标题】:Issue while using channels: all goroutines are asleep - deadlock使用通道时的问题:所有 goroutine 都处于睡眠状态 - 死锁
【发布时间】:2020-12-24 22:39:53
【问题描述】:

我正在尝试了解渠道的运作方式。我有这个例子:

import (
    "fmt"
    "runtime"
    "time"
)

func greet(c chan string) {
    fmt.Println("1: " + <-c)
    fmt.Println("2: " + <-c)
}

func main() {
    fmt.Println("main() started")

    c := make(chan string, 1)

    go greet(c)
    c <- "John"

    c <- "Mike"
    c <- "Mary"
    fmt.Println("active goroutines", runtime.NumGoroutine())
    c <- "Edward"
    time.Sleep(time.Second)
    fmt.Println("main() stopped")
}

在执行上述代码时,我看到它给出了一个错误“致命错误:所有 goroutines 都处于睡眠状态 - 死锁!”。但据我了解,在“爱德华”被发送到频道后,执行应该会被阻止。

c <- "Edward"

这个程序应该打印出 Mary 和 Edward 的值。我也不会在任何地方关闭这个频道。谁能告诉我为什么,我该如何解决这个问题?

【问题讨论】:

  • 在操场上运行这段代码说明你的理解是正确的。 play.golang.org/p/wh2yqht6Snt
  • 您的代码只打印 John 和 Mike,因为您的 goroutine 只有两个打印语句,每个语句都有一个通道接收。您为什么希望它打印更多内容?此外,您可以发送 Mary 而不会导致程序崩溃,因为您使用的是大小为 1 的缓冲通道,并且您不能再发送 Edward,因为没有更多代码可以首先接收 Mary,如果您将大小更改为 2,您将无需添加接收也可以发送 Edward,并且您的程序不会崩溃,但是 Mary 和 Edward 仍然不会被打印,因为您还没有编写任何代码来执行此操作。
  • @mkopriva 那么这是否意味着一旦greet()函数中的所有代码都执行完毕,它将停止运行?
  • 是的,就像任何其他功能一样。 go 关键字并不意味着“无限期地运行这个函数”,如果这就是你的想法的话。

标签: go channels


【解决方案1】:

请参考以下代码,以便您更好地理解它。我也添加了 cmets。虽然,@mkopriva 消除了你的疑虑,但我认为如果我发布评论代码会更好。

package main

import (
    "fmt"
    "time"
)

func greet(c chan string) {
    // Range over the channel until close() is called
    for r := range c {
        // Print the names
        fmt.Println(r)
    }
}

func main() {
    fmt.Println("main() started")
    c := make(chan string, 1) // Make a bufferred channel with capacity of 1
    go greet(c)               // Spawn the goroutine
    c <- "John"               // Send
    c <- "Mike"               // Send
    c <- "Mary"               // Send
    c <- "Edward"             // Send
    close(c)                  // Close the channel which signals the for loop
    // inside greet to stop receiving
    time.Sleep(2 * time.Second) // Let us give the main gorutine some time
    // before it exits so that it lets the greet() to recieve all the names.
    // But it's better to use sync.WaitGroup for this.
    fmt.Println("main() stopped")
}

【讨论】:

  • @sonal 您能否将解决方案标记为已接受?否则,问题将留在未回答的部分。它只是帮助社区快速找到相关答案。
【解决方案2】:

您的问题的答案非常简单。 如果您将某些内容放入频道中,则必须以某种方式将其取回。 函数greet(c chan string) 不会在你只是在它前面放一个“go”时无限运行。这只是意味着该函数正在调用一个新的 goroutine 来运行。 您编写的代码产生了死锁,因为函数在跨越通道后终止。

go greet(c)
c <- "John"
c <- "Mike"
//all these things happen at the same time

当您将某些内容放入无法再次退出的频道时,您会创建死锁。该线程尝试将某些内容放入不再有消费者的通道中。

解决方法也很简单。只需在频道上循环即可。这样它就会在通道上覆盖直到关闭。

func greet(c chan string) {
    for name := range c {
        fmt.Println(name)
    }
}

【讨论】:

    猜你喜欢
    • 2017-02-23
    • 2021-04-05
    • 2013-11-22
    • 1970-01-01
    • 2012-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多