运行下面这段代码输出的结果是什么?
package main
import (
"fmt"
)
func main() {
c := make(chan string) // 创建一个无缓冲的通道
c <- "hello world"
fmt.Println(<-c)
}
答案是:会发生死锁
fatal error: all goroutines are asleep - deadlock!
原因是:对于无缓冲通道,发送方和接收方必须同时准备好才能保证消息的接收。而上面代码发送方和接收方都在 main goroutine
中,所以两者不可能同时运行,必须是一前一后,这也就导致了发送方在一直等待接收方,而接收方由于发送方阻塞而不能执行到,最终的结果是死锁。
发送方在发送消息到通道的时候,此时如果接收方还没有准备好,那么发送方就会一直阻塞着,等待着接收方的到来,如果接收方一直没在就会造成死锁。
如何解决这个问题呢?
我们要知道通道一般适用于不同的协程之间信息的交互,所以要解决上面问题,我们可以让发送方和接收方处于不同的协程中。
见下面代码
func main() {
ch := make(chan string) // 创建一个无缓冲的通道
// 启动一个协程
go func(c chan string) {
c <- "hello world"
}(ch)
fmt.Println(<-ch)
}
输出结果如下:
hello world
而有缓冲通道则不会出现上述的死锁问题
func main() {
c := make(chan string, 1) // 容量为 1 的有缓冲通道
c <- "hello world"
fmt.Println(<-c)
}
运行后可以正常打印结果,所以有缓冲的通道不要求发送方和接收方同时准备好,发送方将消息发送到通道中之后,就可以走了,接收方不管什么时候到,只需通道中消息直接取走就可以了。
但是我们继续看下面这段代码
func main() {
c := make(chan string, 1) // 容量为 1 的有缓冲通道
c <- "hello"
c <- "world"
fmt.Println(<-c)
}
运行结果是什么呢?
fatal error: all goroutines are asleep - deadlock!
为什么有缓冲通道也会有死锁呢?
原因在于此处创建了容量为 1 的缓冲通道,而在代码中连续发送两条消息,当发送第一条消息后通道里面的消息个数就和容量相等了,此时再发送就只能等待通道中的消息被拿走才能继续发送,在通道里面的消息没有取走之前,发送方就会一直阻塞着。
即有缓冲的通道,当通道信息个数等于通道容量时,发送方就会一直阻塞,直到接收方将消息取走,有剩余的容量时发送方才会继续发送。
那么,如果发送发已经发送完毕,接收方一直等待取消息,会发生什么情况?
func main() {
ch := make(chan string)
go func() {
ch <- "hello"
ch <- "world"
}()
for data := range ch {
fmt.Println(data)
}
}
输出结果:
hello
world
fatal error: all goroutines are asleep - deadlock!
又又发生死锁了,这又是为什么呢?
原因就在于发送方发送完成后就不管了,但是呢?接收方又不知道发送方有没有发送完成,就傻乎乎的一直等待下去,最后就造成死锁了。
如何解决呢?
我们只需要在发送方发送完毕后告诉接收方,我已经发送完了,你取了最后一个消息就回家吧,媳妇、儿子等你回家呢。
怎样告诉接收方呢?很简单使用 close()
关闭通道即可。
func main() {
ch := make(chan string)
go func() {
ch <- "hello"
ch <- "world"
close(ch) // 发送完毕后关闭通道
}()
for data := range ch {
fmt.Println(data)
}
}
所以呢,我们也要养成 凡事有交代,件件有着落,事事有回音 的好习惯,要及时反馈当前的状态。