登录
首页 >  Golang >  Go问答

为什么我的简单Go程序出现了死锁?

来源:stackoverflow

时间:2024-03-21 18:30:33 288浏览 收藏

在 Go 程序中,一个简单的程序死锁,因为在 `select` 语句中提供了 `default` 分支。由于 `default` 分支的存在,在 `go ...()` 创建的新 goroutine 执行之前,`select` 语句中的 `case` 分支不太可能被选择。因此,在主 goroutine 尝试写入通道并继续到 `default` 分支之前,被调用的 goroutine 可能还没有准备好从通道中读取。

问题内容

这是我的整个 go 代码!让我困惑的是 case 平衡 <-balance: 没有发生。我不知道为什么?

package main

import (
    "fmt"
)


func main() {

    done := make(chan int)

    var balance int
    balances := make(chan int)
    balance = 1

    go func() {
        fmt.println(<-balances)
        done <- 1
    }()

    select {
    case balances <- balance:
        fmt.println("done case")

    default:
        fmt.println("default case")
    }

    <-done

}
default case
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    /tmp/sandbox575832950/prog.go:29 +0x13d

goroutine 18 [chan receive]:
main.main.func1()
    /tmp/sandbox575832950/prog.go:17 +0x38
created by main.main
    /tmp/sandbox575832950/prog.go:16 +0x97

正确答案


让我困惑的是 case 平衡 <-balance: did 没有发生

具体来说:这是因为 select 带有 default 案例。

每当您使用 go ...() 创建新的 goroutine 时,都无法保证接下来是否会运行调用的 goroutine 或被调用的 goroutine。

实际上,调用 goroutine 中的下一条语句很可能会执行(there being no particularly good reason to stop it)。当然,我们应该编写始终能够正确运行的程序,而不仅仅是部分、大多数,甚至几乎所有时候!使用 go ...() 进行并发编程就是同步 goroutine,以便必须发生预期的行为。如果使用得当,渠道可以做到这一点。

我认为余额通道可以接收数据

它是一个无缓冲通道,因此如果有人从中读取数据,它就可以接收数据。否则,对通道的写入将被阻塞。这让我们回到 select

由于您提供了 default 案例,因此调用 go ...() 的 goroutine 很可能会继续执行,而无法立即选择不同案例的 select 将选择 默认 。因此,在主 goroutine 已经开始尝试写入、失败并继续到 default 情况之前,被调用的 goroutine 不太可能准备好从 balances 读取。

您可以通过从选择中删除默认情况并允许其阻塞直到准备好写入余额来解决此问题。

正如 @schwern 指出的那样,你当然可以。但重要的是,您要了解不一定需要使用 select 来使用通道。而不是只有一种情况的选择,you could instead just write

balances <- balance
fmt.Println("done")

在这种情况下不需要 selectdefault 对您不利,而且只有一种情况,因此不需要 select。您希望在该通道上阻止 main 函数。

您可以向余额添加一个缓冲区,以便在读取之前对其进行写入。

当然。但同样重要的是要理解,通道可能会阻塞发送者和接收者,直到它们都准备好进行通信,这是通道的有效、有用且常见的用法。无缓冲通道不是问题的原因 - 为您的选择提供 default 情况,因此导致意外行为的路径才是原因。

主 goroutine 在匿名 goroutine 函数执行之前执行选择从 balances 接收。主 goroutine 执行 select 中的 default 子句,因为 balances 上没有就绪的接收器。主 goroutine 继续在 done 上接收。

goroutine 在从 balances 接收时阻塞,因为没有发送者。 main 通过采用默认子句继续发送。

主 goroutine 在从 done 接收时阻塞,因为没有发送者。 goroutine 在从 balances 接收时被阻止。

通过将 select 语句替换为 balances <-balance 来修复。 default 子句会导致该问题。当 default 类被删除时,select 中剩余的所有内容都将发送到 balances

今天关于《为什么我的简单Go程序出现了死锁?》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于的内容请关注golang学习网公众号!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>