在 Golang 中,通道(channel)是一种重要的用于 goroutine 间通信的机制。但是如果在使用通道时没有注意避免死锁,会导致程序卡住无法继续执行。
产生死锁的主要原因是: deux goroutines 相互等待对方发送或者接收数据,造成循环等待,程序无法继续执行。
例如:
func main() {
ch := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
go func() {
ch <- 1 // 发送数据
wg.Done()
}()
go func() {
<-ch // 接收数据
ch <- 2 // 发送数据
wg.Done()
}()
wg.Wait()
}
这里启动两个 goroutine,第一个 goroutine 发送 1 到通道,第二个 goroutine 接收这个 1,然后再发送 2 到通道。
但是,第一个 goroutine 在发送 1 之后就结束了,第二个 goroutine 接收到 1 后无法再发送 2,因为没有 goroutine接收它,这就造成了死锁。
所以在使用通道实现 goroutine 间通信时,需要注意两点:
- 发送与接收操作必须成对出现,每次发送必须有对应的接收, vice versa。
- 不要让发送与接收的 goroutine 相互等待。
解决上面的例子,我们可以:
- 让第一个 goroutine 在发送 1 之后也接收通道数据:
``` func() {
ch <- 1 // 发送
<-ch // 接收
wg.Done()
}()
- 启动另一个 goroutine 专门接收第二次发送的数据:
go func() {
<-ch // 接收
ch <- 2 // 发送
wg.Done()
}()
go func() {
<-ch // 接收第二次发送
wg.Done()
}()
- 使用缓冲区避免直接的发送与接收操作相互等待:
ch := make(chan int, 1) // 容量为1
ch <- 1 // 发送
go func() { // 在发送后启动一个接收的goroutine
<-ch
wg.Done()
}()
所以总结来说,避免在使用通道时产生死锁的关键是:
- 发送与接收成对出现
- 避免直接的发送与接收操作产生相互等待
- 可以使用缓冲区或者中间的 goroutine 进行调度
- 尽量避免复杂的通信过程,这会增加死锁发生的概率