62

我确信对于这种微不足道的情况有一个简单的解释,但我是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 <-在 agoroutine中使示例按我们预期的方式运行

package main

import "fmt"

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

再次,为什么?

拜托,我需要深入的解释,而不仅仅是如何消除死锁和修复代码。

4

4 回答 4

99

文档中

如果通道没有缓冲,发送方会阻塞,直到接收方收到该值。如果通道有缓冲区,发送方只会阻塞,直到值被复制到缓冲区;如果缓冲区已满,这意味着要等到某个接收器检索到一个值。

否则说:

  • 当通道已满时,发送方等待另一个 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   
}

示范

于 2013-09-06T14:55:35.083 回答
14

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

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

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

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

这将起作用,因为任务例程正在等待数据被消耗,然后写入发生在无缓冲通道上。

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

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)
}

这将导致死锁

所以简而言之,只有在有一些例程等待从通道读取时才会写入无缓冲通道,否则写入操作将永远阻塞并导致死锁。

注意:同样的概念适用于缓冲通道,但发送方在缓冲区满之前不会被阻塞,这意味着接收方不必与每个写操作同步。

因此,如果我们有大小为 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
}
于 2016-12-17T09:24:38.330 回答
3

在这个答案中,我将尝试解释错误消息,通过它我们可以稍微了解一下 go 在通道和 goroutine 方面是如何工作的

第一个例子是:

package main

import "fmt"

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

错误信息是:

fatal error: all goroutines are asleep - deadlock!

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

  1. 所有的 goroutine 都完成了(睡着了)
  2. 其中一个 goroutine 尝试接收通道

在 #1 的情况下,go 将出错并显示上面的消息,因为现在 go 知道 goroutine 无法接收通道并且它需要一个。

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

于 2017-12-26T14:30:09.550 回答
-2
  • 缓冲消除了同步。
  • 缓冲使它们更像 Erlang 的邮箱。
  • 缓冲通道对于某些问题可能很重要,但它们的推理更微妙
  • 默认情况下,通道是无缓冲的,这意味着
    如果有相应的接收 (<- chan) 准备好接收发送的值,它们将只接受发送 (chan <-)。
  • 缓冲通道接受有限数量的值,而这些值没有相应的接收器。

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

通道上的基本发送和接收是阻塞的。但是,我们可以使用selectwithdefault子句来实现非阻塞的发送、接收,甚至是非阻塞的多路select

于 2019-06-06T07:01:37.953 回答