面试题答案
一键面试避免死锁策略
- 确保发送和接收平衡:
- 在使用
channel
时,要保证发送和接收操作的数量匹配。如果一个channel
只有发送操作而没有接收操作,或者只有接收操作而没有发送操作,就容易导致死锁。例如,在一个生产者 - 消费者模型中,生产者向channel
发送数据,消费者从channel
接收数据,两者的速率要合理匹配。 - 可以使用缓冲
channel
来缓解瞬间的不平衡。比如ch := make(chan int, 10)
,这样在一定程度上可以容纳更多的数据,避免生产者因channel
满而阻塞。
- 在使用
- 使用
select
语句:select
语句可以监听多个channel
的操作,当其中一个channel
准备好时,就执行相应的分支。这在处理多个channel
交互时非常有用。例如:
select { case data := <-ch1: // 处理从ch1接收的数据 case ch2 <- data: // 向ch2发送数据 default: // 当没有任何channel准备好时执行 }
default
分支可以防止select
语句永远阻塞,从而避免死锁。
- 正确的关闭
channel
:- 发送方负责关闭
channel
,接收方通过ok
值来判断channel
是否关闭。例如:
data, ok := <-ch if!ok { // channel已关闭,处理关闭逻辑 }
- 不要在接收方关闭
channel
,否则可能导致数据丢失或死锁。
- 发送方负责关闭
避免饥饿策略
- 公平调度:
- 尽量使用公平的调度策略。例如,在处理多个
channel
的select
语句中,避免让某些channel
总是优先被选中。如果一个channel
在select
语句中的位置靠前,并且总是有数据准备好,那么其他channel
可能会一直得不到执行机会,从而产生饥饿。 - 可以通过随机化
select
语句中channel
的顺序来实现更公平的调度。
- 尽量使用公平的调度策略。例如,在处理多个
- 合理设置优先级:
- 如果确实需要对某些
channel
设置优先级,可以通过在接收数据后进行额外的处理来平衡。比如,给高优先级channel
的数据加上标记,在处理时优先处理,但同时也要定期处理低优先级channel
的数据。
- 如果确实需要对某些
死锁代码示例
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
ch <- 1 // 向无缓冲channel发送数据,但没有接收操作
fmt.Println(<-ch)
}
死锁原因
上述代码中,ch
是一个无缓冲channel
,向其发送数据时会阻塞,直到有接收操作。但这里没有任何接收操作就直接发送数据,所以会导致死锁。
修改方法
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
ch <- 1
close(ch)
}()
fmt.Println(<-ch)
}
这里通过启动一个新的goroutine
来发送数据,并且在发送完成后关闭channel
,主goroutine
从channel
接收数据,避免了死锁。
饥饿代码示例
package main
import (
"fmt"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for i := 0; i < 10000; i++ {
ch1 <- i
}
close(ch1)
}()
for {
select {
case data := <-ch1:
fmt.Println("Received from ch1:", data)
case data := <-ch2:
fmt.Println("Received from ch2:", data)
}
}
}
饥饿原因
ch1
会不断地向channel
发送数据,在select
语句中,只要ch1
有数据,就会优先执行ch1
的接收分支,ch2
几乎没有机会被选中,从而导致ch2
产生饥饿。
修改方法
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for i := 0; i < 10000; i++ {
ch1 <- i
}
close(ch1)
}()
rand.Seed(time.Now().UnixNano())
for {
channels := []chan int{ch1, ch2}
index := rand.Intn(len(channels))
select {
case data := <-channels[index]:
fmt.Printf("Received from ch%d: %d\n", index+1, data)
}
}
}
这里通过随机化select
语句中channel
的选择顺序,使得ch1
和ch2
都有机会被选中,避免了ch2
的饥饿。