面试题答案
一键面试死锁产生的原因
在Go语言中使用channel进行函数间通信时,死锁通常发生在以下情况:
- 发送方等待接收方,而接收方未准备好:当一个goroutine向channel发送数据,但没有其他goroutine在同一时间从该channel接收数据时,发送操作会阻塞。如果此时没有其他并发操作来执行接收,就会发生死锁。
- 接收方等待发送方,而发送方未准备好:类似地,当一个goroutine试图从channel接收数据,但没有其他goroutine向该channel发送数据时,接收操作会阻塞。如果没有其他并发操作来执行发送,也会发生死锁。
- 无缓冲channel双向阻塞:对于无缓冲的channel,发送和接收操作必须同时发生。如果一个goroutine先执行发送,在没有接收方准备好的情况下会阻塞;反之,如果一个goroutine先执行接收,在没有发送方准备好的情况下也会阻塞。如果这种双向阻塞发生在没有其他并发操作打破僵局时,就会导致死锁。
生产者 - 消费者模型中避免死锁示例
package main
import (
"fmt"
)
func producer(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i // 生产者向channel发送数据
fmt.Printf("Produced: %d\n", i)
}
close(ch) // 生产完成,关闭channel
}
func consumer(ch chan int) {
for num := range ch { // 使用for - range循环从channel接收数据,直到channel关闭
fmt.Printf("Consumed: %d\n", num)
}
}
func main() {
ch := make(chan int)
go producer(ch)
go consumer(ch)
// 防止main函数退出,这里可以使用更优雅的方式等待goroutine完成,如sync.WaitGroup
select {}
}
在上述代码中:
- 生产者:
producer
函数负责向ch
这个channel发送数据,发送完成后关闭channel。关闭channel是一个重要的操作,它可以让接收方知道数据发送已经结束,从而避免接收方无限期阻塞。 - 消费者:
consumer
函数使用for - range
循环从ch
接收数据。这种方式会持续接收数据直到channel关闭,确保不会因为channel无数据而一直阻塞(在channel关闭前)。 - 主函数:在
main
函数中,创建了ch
这个channel,并启动了生产者和消费者的goroutine。通过select {}
语句防止main
函数立即退出,从而保证生产者和消费者有足够的时间执行。
通过合理地关闭channel、使用for - range
循环接收数据以及确保goroutine的正确调度,可以有效地避免在生产者 - 消费者模型中使用channel时出现死锁。