golang的chan用法与fatal error: all goroutines are asleep - deadlock!

时间:2022-12-12 12:57:57

例子1:

func main() {
ch := make(chan int)
ch <- 1
fmt.Println("发送成功")
}

上面这段代码能够通过编译,但是执行的时候会出现以下错误:

golang的chan用法与fatal error: all goroutines are asleep - deadlock!

为什么会出现deadlock错误呢?

因为我们使用ch := make(chan int)创建的是无缓冲的通道,无缓冲的通道只有在有人接收值的时候才能发送值。就像你住的小区没有快递柜和代收点,快递员给你打电话必须要把这个物品送到你的手中,简单来说就是无缓冲的通道必须有接收才能发送。

上面的代码会阻塞在ch <- 1这一行代码形成死锁,那如何解决这个问题呢?

一种方法是启用一个goroutine去接收值,例2:

package main
import "fmt"
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)

go recv(ch) // 启用goroutine从通道接收值
ch <- 1
fmt.Println("发送成功")
}

golang的chan用法与fatal error: all goroutines are asleep - deadlock!

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。

使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。

我们把上面的例子修改一下,把ch<- 1放到启动另一个goroutine之前。

例子3

package main
import "fmt"
func recv(c chan int) {
ret := <-c
fmt.Println("接收成功", ret)
}
func main() {
ch := make(chan int)
ch <- 1 //放到go recv之前。导致
go recv(ch) // 启用goroutine从通道接收值

fmt.Println("发送成功")
}

golang的chan用法与fatal error: all goroutines are asleep - deadlock!

ch <- 1 //放到go recv之前。导致deadlock。在无缓冲通道赋值之前,必须先启动接收通道goroutine。要不然同样发生deadlock

例子4:当一个通道被close后,再往该通道发送,会发生panic。从关闭的通道获取值时,会得到类型零值。

package main
import "fmt"
// channel
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
// 开启goroutine将0~10的数发送到ch1中
go func() {
for i := 0; i < 10; i++ {
ch1 <- i
}
close(ch1)
}()
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
go func() {
//这里会无限循环获取ch1通道的值。由于ch1已经关闭,后面获取就是int类型的0值
for {
i := <-ch1
ch2 <- i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}

golang的chan用法与fatal error: all goroutines are asleep - deadlock!

例子5:一个通道被关闭后,怎么样来判断通道被关闭,取消获取该通道的goroutine等待呢?这里我们使用i,ok:=<-ch1,ok获取为false时,即是ch1已经关闭。

package main
import "fmt"
// channel
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
// 开启goroutine将0~10的数发送到ch1中
go func() {
for i := 0; i < 10; i++ {
ch1 <- i
}
close(ch1)
}()
// 开启goroutine从ch1中接收值,并将该值的平方发送到ch2中
go func() {
//这里会无限循环获取ch1通道的值。由于ch1已经关闭,后面获取就是int类型的0值
for {
i,ok := <-ch1 // 通道关闭后再取值ok=false
if !ok{
break
}
ch2 <- i
}
close(ch2)
}()
// 在主goroutine中从ch2中接收值打印
for i := range ch2 { // 通道关闭后会退出for range循环
fmt.Println(i)
}
}

golang的chan用法与fatal error: all goroutines are asleep - deadlock!

例子6:单向通道:有的时候我们会将通道作为参数在多个任务函数间传递,很多时候我们在不同的任务函数中使用通道都会对其进行限制,比如限制通道在函数中只能发送或只能接收。​

func counter(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i
}
close(out)
}
func printer(in <-chan int) {
for i := range in {
fmt.Println(i)
}
}
func main() {
ch1 := make(chan int)

go counter(ch1)
printer(ch1)
}

golang的chan用法与fatal error: all goroutines are asleep - deadlock!

在函数传参及任何赋值操作中将双向通道转换为单向通道是可以的,但反过来是不可以的。