面试题答案
一键面试使用select语句实现对多个chan的多路复用
在Go语言中,select
语句用于在多个通信操作(如从通道接收数据或向通道发送数据)之间进行选择。其语法结构如下:
select {
case <-chan1:
// 当chan1有数据可读时执行这里
case chan2 <- value:
// 当chan2可以接收数据时执行这里
default:
// 当上面的case都不满足时执行这里(可选)
}
在一个select
语句中,Go运行时会阻塞,直到其中一个case
可以继续执行(即某个通道可以进行读或写操作)。如果多个case
都准备好,Go会随机选择一个执行。
死锁情况分析
- 所有通道阻塞:当
select
语句中所有case
涉及的通道操作都无法立即进行(即没有通道准备好读或写),并且没有default
分支时,就会发生死锁。例如:
package main
import "fmt"
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
select {
case <-ch1:
fmt.Println("Received from ch1")
case <-ch2:
fmt.Println("Received from ch2")
}
}
在上述代码中,ch1
和ch2
都没有数据可读,且没有default
分支,所以select
语句会一直阻塞,导致死锁。
2. 循环中通道操作不匹配:在循环中,如果通道的发送和接收操作不匹配,也可能导致死锁。例如:
package main
import "fmt"
func main() {
ch := make(chan int)
for i := 0; i < 2; i++ {
select {
case ch <- i:
fmt.Printf("Sent %d to ch\n", i)
}
}
// 这里没有从ch读取数据的操作,ch会一直阻塞在发送操作上,最终导致死锁
}
避免死锁的方法
- 添加default分支:在
select
语句中添加default
分支,可以避免因所有通道阻塞而导致的死锁。default
分支会在所有case
都无法立即执行时立即执行。例如:
package main
import "fmt"
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
select {
case <-ch1:
fmt.Println("Received from ch1")
case <-ch2:
fmt.Println("Received from ch2")
default:
fmt.Println("No channel is ready")
}
}
- 确保通道操作匹配:在循环中使用通道时,要确保通道的发送和接收操作在数量和逻辑上匹配。例如:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 2; i++ {
ch <- i
}
close(ch)
}()
for val := range ch {
fmt.Printf("Received %d from ch\n", val)
}
}
在上述代码中,通过在一个goroutine中向通道发送数据,并在主goroutine中使用for... range
从通道接收数据,直到通道关闭,确保了通道操作的匹配,避免了死锁。同时,及时关闭通道也很重要,它可以通知接收方不再有数据发送。