面试题答案
一键面试利用select语句实现高效多路复用示例
package main
import (
"fmt"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
ch1 <- 10
}()
go func() {
ch2 <- 20
}()
select {
case value := <-ch1:
fmt.Printf("Received from ch1: %d\n", value)
case value := <-ch2:
fmt.Printf("Received from ch2: %d\n", value)
}
}
在上述示例中,select
语句同时监听 ch1
和 ch2
两个通道。一旦其中任意一个通道有数据可读,对应的 case
分支就会被执行。
多路复用过程中可能出现的阻塞和竞争条件
- 阻塞:
- 如果所有
select
中的case
语句都无法立即执行(例如,所有通道都没有数据可读或不可写),select
语句就会阻塞,直到有一个case
可以执行。 - 如果只有一个通道在
select
中,并且该通道没有数据可读或不可写,select
就会一直阻塞,除非设置了default
分支。
- 如果所有
- 竞争条件:
- 当多个
goroutine
同时向同一个通道发送数据,或者从同一个通道接收数据时,可能会出现竞争条件。例如,如果多个goroutine
同时向一个已满的通道发送数据,就会导致某些发送操作被阻塞,从而出现不确定的行为。
- 当多个
避免阻塞和竞争条件的方法
- 避免阻塞:
- 设置
default
分支:在select
语句中添加default
分支可以避免select
语句永远阻塞。例如:
- 设置
select {
case value := <-ch1:
fmt.Printf("Received from ch1: %d\n", value)
case value := <-ch2:
fmt.Printf("Received from ch2: %d\n", value)
default:
fmt.Println("None of the channels are ready yet.")
}
- 使用带缓冲的通道:创建带缓冲的通道可以减少阻塞的可能性。例如
ch := make(chan int, 10)
创建了一个容量为10的缓冲通道,在缓冲区未满时发送操作不会阻塞。
- 避免竞争条件:
- 使用互斥锁:可以使用
sync.Mutex
来保护共享资源,包括通道。例如,如果多个goroutine
需要向同一个通道发送数据,可以在发送操作前后加锁:
- 使用互斥锁:可以使用
var mu sync.Mutex
ch := make(chan int)
go func() {
mu.Lock()
ch <- 10
mu.Unlock()
}()
- 使用通道本身的特性:合理设计通道的使用方式,确保每个通道在同一时间只有一个
goroutine
进行写操作,或只有一个goroutine
进行读操作。例如,可以通过将通道作为参数传递给特定的goroutine
,让该goroutine
负责对通道的写操作,其他goroutine
只进行读操作。