当我的简单Go程序运行时,为什么结果是死锁?

huangapple go评论78阅读模式
英文:

When my simple Go program run ,Why the result is deadlock?

问题

这是我的整个Go代码!让我困惑的是case balances <- 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
英文:

This is my entire Go code! What confused me is that case balances &lt;- balance: did't occurs.I dont know why?

package main

import (
	&quot;fmt&quot;
)


func main() {

	done := make(chan int)

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

	go func() {
		fmt.Println(&lt;-balances)
		done &lt;- 1
	}()

	select {
	case balances &lt;- balance:
		fmt.Println(&quot;done case&quot;)

	default:
		fmt.Println(&quot;default case&quot;)
	}

	&lt;-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

答案1

得分: 4

主 goroutine 在匿名 goroutine 函数执行从 balances 接收之前执行了 select 语句。主 goroutine 在 select 中执行了 default 分支,因为 balances 上没有准备好的接收者。主 goroutine 继续执行接收 done 的操作。

由于没有发送者,goroutine 在从 balances 接收时被阻塞。主 goroutine 继续执行了默认分支,跳过了发送操作。

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

通过用 balances <- balance 替换 select 语句来修复问题。问题出在 default 分支上。当移除 default 分支后,select 中只剩下向 balances 发送的操作。

英文:

The main goroutine executes the select before the anonymous goroutine function executes the receive from balances. The main goroutine executes the default clause in the select because there is no ready receiver on balances. The main goroutine continues on to receive on done.

The goroutine blocks on receive from balances because there is no sender. Main continued past the send by taking the default clause.

The main goroutine blocks on receive from done because there is no sender. The goroutine is blocked on receive from balances.

Fix by replacing the select statement with balances &lt;- balance. The default clause causes the problem. When the the default class is removed, all that remains in the select is send to balances.

答案2

得分: 3

由于并发性,不能保证goroutine在select之前执行。我们可以通过在goroutine中添加打印语句来观察这一点。

    go func() {
        fmt.Println("Here")
        fmt.Println(<-balances)
        done <- 1
    }()
$ go run test.go
default case
Here
fatal error: all goroutines are asleep - deadlock!
...

如果select先执行,balances <- balance会被阻塞;balances没有缓冲区,也没有任何尝试从中读取的内容。case balances <- balance会被阻塞,所以select跳过它并执行默认情况。

然后goroutine运行并阻塞读取balances。与此同时,主代码阻塞读取done。死锁。


你可以通过从select中删除默认情况并允许其阻塞,直到balances准备好写入来解决这个问题。

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

或者你可以给balances添加一个缓冲区,这样它就可以在被读取之前被写入。然后case balances <- balance不会被阻塞。

balances := make(chan int, 1)
英文:

Because of concurrency, there's no guarantee that the goroutine will execute before the select. We can see this by adding a print to the goroutine.

    go func() {
        fmt.Println(&quot;Here&quot;)
        fmt.Println(&lt;-balances)
        done &lt;- 1
    }()
$ go run test.go
default case
Here
fatal error: all goroutines are asleep - deadlock!
...

If the select runs first, balances &lt;- balance would block; balances has no buffer and nothing is trying to read from it. case balances &lt;- balance would block so select skips it and executes its default.

Then the goroutine runs and blocks reading balances. Meanwhile the main code blocks reading done. Deadlock.


You can solve this by either removing the default case from the select and allowing it to block until balances is ready to be written to.

    select {
        case balances &lt;- balance:
            fmt.Println(&quot;done case&quot;)
    }

Or you can add a buffer to balances so it can be written to before it is read from. Then case balances &lt;- balance does not block.

balances := make(chan int, 1)

答案3

得分: 2

我困惑的是 case balances <- balance 没有发生的原因。

具体来说,这是因为 selectdefault case 有关。

每当你使用 go ...() 创建一个新的 goroutine 时,无法保证是调用的 goroutine 运行还是调用它的 goroutine 运行。

实际上,调用的 goroutine 很可能会继续执行(没有特别好的理由停止它)。当然,我们应该编写能够始终正确运行的程序,而不仅仅是有时、大多数或几乎所有的时间!使用 go ...() 进行并发编程的关键在于同步 goroutine,以确保预期的行为必须发生。如果使用正确,通道可以实现这一点。

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

由于你提供了一个 default case,调用 go ...() 的 goroutine 很可能会继续执行,并且 select 在无法立即选择其他 case 时会选择 default。因此,在主 goroutine 已经尝试写入 balances、失败并转到 default case 之前,被调用的 goroutine 准备好从 balances 中读取的可能性非常小。

你可以通过从 select 中删除 default case 并允许它阻塞,直到 balances 准备好被写入来解决这个问题。

正如 @Schwern 指出的那样,你确实可以这样做。但是重要的是要理解,你不一定需要使用 select 来使用通道。你可以使用以下代码替代只有一个 case 的 select

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

在这种情况下,不需要使用 selectdefault case 会对你产生负面影响,而且除了一个 case 外,没有其他需要使用 select 的情况。你希望主函数在该通道上阻塞。

你也可以给 balances 添加缓冲区,这样在读取之前就可以写入它。

当然可以。但是再次强调,重要的是要理解,通道可能会阻塞发送者和接收者,直到它们都准备好进行通信,这是通道的一个有效、有用且常见的用法。无缓冲通道并不是你问题的原因,问题的原因是为 select 提供了 default case,从而导致意外行为的发生。

英文:

> What confused me is that case balances <- balance: did't occurs

To be specific: it's because of select with a default case.

Whenever you create a new goroutine with go ...(), there is no guarantee about whether the invoking goroutine, or the invoked goroutine, will run next.

In practice it's likely that the next statements in the invoking goroutine will execute next (there being no particularly good reason to stop it). Of course, we should write programs that function correctly all the time, not just some, most, or even almost all the time! Concurrent programming with go ...() is all about synchronizing the goroutines so that the intended behavior must occur. Channels can do that, if used properly.

> I think the balances channel can receive data

It's an unbuffered channel, so it can receive data if someone is reading from it. Otherwise, that write to the channel will block. Which brings us back to select.

Since you provided a default case, it's quite likely that the goroutine that invoked go ...() will continue to execute, and select that can't immediately choose a different case, will choose default. So it would be very unlikely for the invoked goroutine to be ready to read from balances before the main goroutine had already proceeded to try to write to it, failed, and gone on to the default case.

> You can solve this by either removing the default case from the select and allowing it to block until balances is ready to be written to.

You sure can, as @Schwern points out. But it's important that you understand you don't necessarily need to use select to use channels. Instead of a select with just one case, you could instead just write

balances &lt;- balance
fmt.Println(&quot;done&quot;)

select is not required in this case, default is working against you, and there's just one case otherwise, so there's no need for select. You want the main function to block on that channel.

> you can add a buffer to balances so it can be written to before it is read from.

Sure. But again, important to understand that the fact that a channel might block both sender and receiver until they're both ready to communicate , is a valid, useful, and common use of channels. Unbuffered channels are not the cause of your problem - providing a default case for your select, and thus a path for unintended behavior, is the cause.

huangapple
  • 本文由 发表于 2021年11月4日 12:04:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/69834412.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定