为什么在同一个goroutine中使用无缓冲通道会导致死锁

时间:2013-09-06 14:47:05

标签: concurrency go channels

我确信这个微不足道的情况有一个简单的解释,但我是go并发模型的新手。

当我运行这个例子时

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

我收到此错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    /home/tarrsalah/src/go/src/github.com/tarrsalah/tour.golang.org/65.go:8 +0x52
exit status 2

为什么?


c <-中包装goroutine会使示例按预期运行

package main

import "fmt"

func main() {
    c := make(chan int)        
    go func(){
       c <- 1
    }()
    fmt.Println(<-c)
}

再次,为什么?

请,我需要深入解释,而不仅仅是如何消除死锁并修复代码。

4 个答案:

答案 0 :(得分:67)

来自the documentation

  

如果通道未缓冲,则发送方将阻塞,直到接收方收到该值。如果通道具有缓冲区,则发送方仅阻止该值   已被复制到缓冲区;如果缓冲区已满,这意味着   等到某个接收者检索到一个值。

另有说法:

  • 当频道已满时,发送者会等待另一个goroutine通过接收
  • 来腾出空间
  • 你可以看到一个无缓冲的频道作为一个完整的频道:必须有另一个goroutine来接收发送者发送的内容。

这一行

c <- 1

阻塞,因为通道是无缓冲的。由于没有其他goroutine接收该值,情况无法解决,这是一个僵局。

您可以通过将频道创建更改为

来使其无法阻止
c := make(chan int, 1) 

这样在频道阻止之前,频道中就有一个项目可以容纳。

但这不是并发性的意义所在。通常情况下,您不会使用没有其他goroutine的通道来处理您放入的内容。你可以像这样定义一个接收goroutine:

func main() {
    c := make(chan int)    
    go func() {
        fmt.Println("received:", <-c)
    }()
    c <- 1   
}

Demonstration

答案 1 :(得分:10)

在无缓冲通道中写入通道不会发生,直到必须有一些接收器等待接收数据,这意味着在下面的例子中

func main(){
    ch := make(chan int)
    ch <- 10   /* Main routine is Blocked, because there is no routine to receive the value   */
    <- ch
}

现在如果我们有其他常规,同样的原则适用

func main(){
  ch :=make(chan int)
  go task(ch)
  ch <-10
}
func task(ch chan int){
   <- ch
}

这将起作用,因为 task 例程正在等待在写入无缓冲通道之前消耗数据。

为了更清楚,让我们在主函数中交换第二和第三语句的顺序。

func main(){
  ch := make(chan int)
  ch <- 10       /*Blocked: No routine is waiting for the data to be consumed from the channel */
  go task(ch)
}

这将导致死锁

因此,简而言之,只有当某些例程等待从通道读取时才会发送对无缓冲通道的写入,否则写入操作将永久阻塞并导致死锁。

注意:相同的概念适用于缓冲通道,但在缓冲区已满之前,Sender不会被阻塞,这意味着接收器不必与每次写入操作同步。

因此,如果我们缓冲了大小为1的通道,那么上面提到的代码将起作用

func main(){
  ch := make(chan int, 1) /*channel of size 1 */
  ch <-10  /* Not blocked: can put the value in channel buffer */
  <- ch 
}

但是如果我们在上面的例子中写了更多的值,那么就会发生死锁

func main(){
  ch := make(chan int, 1) /*channel Buffer size 1 */
  ch <- 10
  ch <- 20 /*Blocked: Because Buffer size is already full and no one is waiting to recieve the Data  from channel */
  <- ch
  <- ch
}

答案 2 :(得分:1)

在这个答案中,我将尝试解释错误信息,通过该信息我们可以了解一下如何在频道和goroutines方面发挥作用

第一个例子是:

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}

错误消息是:

fatal error: all goroutines are asleep - deadlock!

在代码中,根本没有goroutine(BTW此错误在运行时,而不是编译时)。当go运行此行c <- 1时,它希望确保在某个地方(即<-c)收到频道中的消息。 Go不知道此时是否会收到频道。所以go将等待运行的goroutines完成,直到发生以下任何一种情况:

  1. 所有goroutine都已完成(睡着了)
  2. 其中一个goroutine尝试接收频道
  3. 在#1的情况下,go会因上面的消息而出错,因为现在知道goroutine无法接收该频道而且需要一个。

    在#2情况下,程序将继续,因为现在知道收到了这个频道。这解释了OP示例中的成功案例。

答案 3 :(得分:0)

  • 缓冲会删除同步。
  • 缓冲使它们更像Erlang的邮箱。
  • 缓冲的通道对于某些问题可能很重要,但是对于
  • 而言,它们更难以解释
  • 默认情况下,默认通道是无缓冲的,这意味着它们将仅接受发送
    (chan <-),如果有相应的接收(<-chan)准备接收发送的值。
  • 缓冲的通道接受有限数量的 没有相应接收者的值。

messages:= make(chan string,2)//-缓冲最多2个值的字符串通道。

通道上的基本发送和接收被阻止。 但是,我们可以在select子句中使用default来实现非阻塞发送,接收,甚至是非阻塞多路select。 / p>