面试题答案
一键面试可能导致死锁的原因
- 通道未缓冲或缓冲不足:如果发送方在没有接收方准备好接收数据时向无缓冲通道发送数据,或者缓冲通道已满且发送方继续发送,都会导致发送方阻塞。同理,接收方在通道无数据时接收也会阻塞。例如,负责读取数据的goroutine向处理数据的goroutine发送数据的通道无缓冲,而处理数据的goroutine此时还未准备好接收,就会出现发送方阻塞。
- 发送和接收的顺序问题:如果所有goroutine都在等待通道操作(发送或接收),而没有一个能够主动发起有效的操作,就会形成死锁。比如,处理数据的goroutine在处理完数据后等待结果反馈通道有接收方,而负责接收反馈结果的goroutine在等待处理数据的goroutine发送数据,双方都在等待对方先行动。
- goroutine过早结束:负责读取数据的goroutine如果过早结束,而其他依赖其发送数据的goroutine还在等待接收数据,就会导致死锁。例如,读取数据的goroutine在读取完部分数据后意外退出,而其他处理数据的goroutine还在等待接收剩余数据。
解决方案
- 合理设置通道缓冲:给发送数据的通道和反馈结果的通道设置适当的缓冲。例如:
dataCh := make(chan Data, 10) // 假设Data是数据类型,设置缓冲为10
resultCh := make(chan Result, 10) // 假设Result是结果类型,设置缓冲为10
- 确保正确的发送和接收顺序:通过使用
sync.WaitGroup
等同步机制,确保goroutine按照正确的顺序执行通道操作。例如:
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
data := readData()
dataCh <- data
}()
go func() {
defer wg.Done()
data := <-dataCh
result := processData(data)
resultCh <- result
}()
wg.Wait()
- 处理goroutine的异常结束:在负责读取数据的goroutine中使用
recover
来捕获异常,避免其意外结束。例如:
go func() {
defer func() {
if r := recover(); r != nil {
// 处理异常,例如关闭通道
close(dataCh)
}
}()
for {
data, err := readDataFromSource()
if err != nil {
break
}
dataCh <- data
}
close(dataCh)
}()
同时,在接收数据的goroutine中使用for... range
来读取通道数据,确保在通道关闭时能正确退出。例如:
go func() {
for data := range dataCh {
result := processData(data)
resultCh <- result
}
close(resultCh)
}()