面试题答案
一键面试select
语句避免死锁的原理和作用
- 原理:
select
语句会阻塞,直到其内部的某个case
语句可以执行。它会同时等待多个通道操作(发送或接收)准备就绪。如果多个case
都准备就绪,会随机选择其中一个执行。当没有任何case
可以执行,并且没有default
分支时,select
语句就会一直阻塞,这避免了因为通道操作无法立即完成而导致的死锁。因为程序不会继续执行可能导致死锁的后续代码,而是等待直到通道操作可以进行。 - 作用:
select
语句主要用于在多个通道之间进行多路复用,协调不同的并发操作。它允许程序优雅地处理多个异步事件,确保在处理通道操作时不会因为资源竞争或等待而陷入死锁状态,从而提高程序的并发性能和稳定性。
可能导致死锁的陷阱场景及避免方法
场景一:无 default
分支且所有 case
阻塞
package main
import (
"fmt"
)
func main() {
var ch1 chan int
var ch2 chan int
select {
case <-ch1:
fmt.Println("Received from ch1")
case <-ch2:
fmt.Println("Received from ch2")
}
}
分析:在这个例子中,ch1
和 ch2
都是未初始化的通道,因此两个 case
都会阻塞。由于没有 default
分支,select
语句会永远阻塞,导致死锁。
避免方法:添加 default
分支,或者确保至少有一个通道已经准备好进行操作。
package main
import (
"fmt"
)
func main() {
var ch1 chan int
var ch2 chan int
select {
case <-ch1:
fmt.Println("Received from ch1")
case <-ch2:
fmt.Println("Received from ch2")
default:
fmt.Println("No channel is ready")
}
}
场景二:所有 case
都在同一个 goroutine 中发送和接收
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
select {
case ch <- 1:
fmt.Println("Sent 1 to ch")
case val := <-ch:
fmt.Println("Received", val, "from ch")
}
}
分析:在这个例子中,select
语句在同一个 goroutine 中尝试向通道 ch
发送数据和从通道 ch
接收数据。如果先执行发送操作,它会阻塞等待另一个 goroutine 来接收;如果先执行接收操作,它会阻塞等待另一个 goroutine 来发送,这就导致了死锁。
避免方法:将发送和接收操作放在不同的 goroutine 中。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
ch <- 1
}()
select {
case val := <-ch:
fmt.Println("Received", val, "from ch")
}
}