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

tar*_*lah 46 concurrency go channels

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

当我运行这个例子

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

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
Run Code Online (Sandbox Code Playgroud)

为什么?


包装c <-在一个goroutine使得示例运行正如我们预期的那样

package main

import "fmt"

func main() {
    c := make(chan int)        
    go func(){
       c <- 1
    }()
    fmt.Println(<-c)
}
Run Code Online (Sandbox Code Playgroud)

再次,为什么?

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

Den*_*ret 72

文档:

如果通道未缓冲,则发送方将阻塞,直到接收方收到该值.如果通道有缓冲区,则发送方仅阻塞,直到将值复制到缓冲区为止; 如果缓冲区已满,则表示等待某个接收方检索到某个值.

说不然:

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

这条线

c <- 1
Run Code Online (Sandbox Code Playgroud)

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

您可以通过将频道创建更改为来阻止它

c := make(chan int, 1) 
Run Code Online (Sandbox Code Playgroud)

这样在频道阻挡之前,频道中就有一个项目的空间.

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

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

示范

  • 当频道已满时,必须有一个goroutine来接收发送者发送的内容,否则发送者会阻塞直到一个人来.您可以将无缓冲的频道视为始终完整的频道. (16认同)
  • 试着将通道想象成一个物理管道:如果它太短或者已经满了,你必须等待另一端的某个人拿东西或者把东西放进去会使一些内容落在地上. (9认同)
  • 我发现很难想到,有三种情况:1)没有新goroutine的无缓冲通道 - >死锁,2)没有新goroutine的缓冲通道 - >没有死锁3)带有新goroutine的无缓冲通道 - >运行. (3认同)
  • +1"无缓冲通道作为始终完整通道". (3认同)

bha*_*atj 10

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

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

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

func main(){
  ch :=make(chan int)
  go task(ch)
  ch <-10
}
func task(ch chan int){
   <- ch
}
Run Code Online (Sandbox Code Playgroud)

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

为了更清楚,让我们在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)
}
Run Code Online (Sandbox Code Playgroud)

这将导致死锁

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

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

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

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

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

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
}
Run Code Online (Sandbox Code Playgroud)

  • **这不是正确的**:_“在无缓冲通道中,只有在必须有一些接收器正在等待接收数据的情况下,才会写入该通道” _。写入通道将发生,当接收器出现时,它将接收数据,如果没有接收到,则死锁或泄漏等。 (2认同)

Chu*_*ang 5

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

第一个例子是:

package main

import "fmt"

func main() {
    c := make(chan int)    
    c <- 1   
    fmt.Println(<-c)
}
Run Code Online (Sandbox Code Playgroud)

错误信息是:

fatal error: all goroutines are asleep - deadlock!
Run Code Online (Sandbox Code Playgroud)

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

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

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

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