面试题答案
一键面试常见导致死锁的场景
- 双向通道通信:两个 goroutine 通过通道进行双向通信时,如果顺序不当可能死锁。例如:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 1 // 发送数据到通道
fmt.Println(<-ch) // 尝试从通道接收数据
}()
fmt.Println(<-ch) // 主 goroutine 尝试从通道接收数据
ch <- 2 // 尝试发送数据到通道
}
在上述代码中,两个 goroutine 都先尝试接收数据,而没有一个先发送数据,导致死锁。
- 未缓冲通道的循环通信:在使用未缓冲通道进行循环通信时,如果发送和接收操作的次数不匹配,可能导致死锁。例如:
package main
import "fmt"
func main() {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func() {
ch <- i
}()
}
for i := 0; i < 2; i++ {
fmt.Println(<-ch)
}
}
这里有 3 个 goroutine 尝试向通道发送数据,但只有 2 次接收操作,最后一个发送操作会导致死锁。
避免死锁的方法
- 合理安排通信顺序:在双向通道通信场景下,确保至少有一个 goroutine 先进行发送操作。例如:
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 1 // 先发送数据
fmt.Println(<-ch) // 再接收数据
}()
fmt.Println(<-ch) // 主 goroutine 接收数据
ch <- 2 // 主 goroutine 发送数据
}
- 确保发送和接收操作的平衡:在使用未缓冲通道循环通信时,要保证发送和接收操作的次数一致。例如:
package main
import "fmt"
func main() {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func() {
ch <- i
}()
}
for i := 0; i < 3; i++ {
fmt.Println(<-ch)
}
}
或者可以使用带缓冲的通道,避免因接收不及时导致发送操作阻塞。例如:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
for i := 0; i < 3; i++ {
go func() {
ch <- i
}()
}
for i := 0; i < 2; i++ {
fmt.Println(<-ch)
}
}
这样前两个数据可以先存入缓冲通道,不会立即导致死锁。