[英]Why using unbuffered channel in the same goroutine gives a deadlock
我确信这个微不足道的情况有一个简单的解释,但我是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
为什么?
在goroutine
包装c <-
使得示例按照我们的预期运行
package main
import "fmt"
func main() {
c := make(chan int)
go func(){
c <- 1
}()
fmt.Println(<-c)
}
再次,为什么?
请,我需要深入解释,而不仅仅是如何消除死锁并修复代码。
从文档 :
如果通道未缓冲,则发送方将阻塞,直到接收方收到该值。 如果通道有缓冲区,则发送方仅阻塞,直到将值复制到缓冲区为止; 如果缓冲区已满,则表示等待某个接收方检索到某个值。
说不然:
这条线
c <- 1
块因为通道是无缓冲的。 由于没有其他goroutine接收该值,情况无法解决,这是一个僵局。
您可以通过将频道创建更改为来阻止它
c := make(chan int, 1)
这样在频道阻挡之前,频道中就有一个项目的空间。
但这不是并发性的意义所在。 通常情况下,您不会使用没有其他goroutine的通道来处理您放入的内容。 你可以像这样定义一个接收goroutine:
func main() {
c := make(chan int)
go func() {
fmt.Println("received:", <-c)
}()
c <- 1
}
在无缓冲通道中写入通道不会发生,直到必须有一些接收器等待接收数据,这意味着在下面的例子中
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
}
这将起作用,因为任务例程正在等待在写入无缓冲通道之前消耗数据。
为了更清楚,让我们在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
}
在这个答案中,我将尝试解释错误信息,通过它我们可以窥探一下如何在通道和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的情况下,go会出现上面的消息错误,因为现在知道goroutine没有办法收到频道而且需要一个。
在#2的情况下,程序将继续,因为现在知道接收到该频道。 这解释了OP示例中的成功案例。
messages:= make(chan string,2)// - 字符串缓冲最多2个值。
通道上的基本发送和接收是阻塞的。 但是,我们可以使用带有default
子句的select
来实现非阻塞发送,接收,甚至是非阻塞的多路select
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.