多路复用:select
语句用于在多个发送/接收信道操作中进行选择。select
语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select
会随机地选取其中之一执行。该语法与 switch
类似,所不同的是,这里的每个 case
语句都是信道操作。
func main() {
var (
ch1 = make(chan string)
ch2 = make(chan string)
)
go test2(ch1)
go test3(ch2)
select {
case res := <-ch1:
fmt.Println(res)
case res := <-ch2:
fmt.Println(res)
}
}
func test2(ch chan string) {
fmt.Println("我是test2")
time.Sleep(3*time.Second) // 等待两个goroutine都执行完
ch <- "test2的数据"
}
func test3(ch chan string) {
fmt.Println("我是test3")
time.Sleep(3*time.Second) // 等待两个goroutine都执行完
ch <- "test3的数据"
}
上述示例代码,使用 select
等待信道中传递过来值,一旦其中一个信道接收到值就会运行 case 下的代码块。
所以,多次运行,结果不会一模一样,如果是同时多个信道中接收到值,会随机选择一个执行。
在没有 case 准备就绪时,可以执行 select
语句中的默认情况(Default Case)。这通常用于防止 select
语句一直阻塞。
select {
case res := <-ch1:
fmt.Println(res)
case res := <-ch2:
fmt.Println(res)
default:
fmt.Println("default的情况")
time.Sleep(time.Second)
}
在上方 select
代码块中加入default后,因为 case 都没有准备就绪,因此会直接执行default下的代码块。
但是我们使用 default 一般都是配合 for 循环来使用。
count := 0
for {
select {
case res := <-ch1:
fmt.Println(res)
case res := <-ch2:
fmt.Println(res)
default:
fmt.Println("default的情况")
time.Sleep(time.Second)
}
count++
if count > 10 {
break
}
}
因为 select
是和 channel 一起使用的,因此使用不当就有可能造成死锁现象。
func main() {
ch := make(chan string)
select {
case <-ch:
fmt.Println("死锁")
}
}
但是加上 默认情况后就能避免死锁的发生, 因为没有 准备就绪 case
,就会执行 default
下的代码块
func main() {
ch := make(chan string)
select {
case <-ch:
fmt.Println("死锁")
default:
fmt.Println("我是default")
}
}
并且select
只含有值为 nil
的信道,也同样会执行默认情况。
func main() {
var ch chan string // channel 是引用类型,定义,值为nil
select {
case <- ch:
fmt.Println("死锁")
default:
fmt.Println("我是default")
}
}
我们在前面已经知道,除非有 case 执行,select 语句就会一直阻塞着。在这里,select
语句没有任何 case,因此它会一直阻塞,导致死锁。
func main() {
select {}
}
判断信道中有没有存满
var wg1 sync.WaitGroup
func main() {
output1 := make(chan string, 5) // 长度为5的有缓冲信道
// 子协程写数据
wg1.Add(1)
go write(output1)
wg1.Wait()
}
func write(ch chan string) {
for {
select {
case ch <- "hello": // 往信道中写hello
fmt.Println("write hello")
default:
fmt.Println("信道满了")
wg1.Done()
break // 标准写法,要写break,整个goroutine就会结束,防止浪费资源
}
time.Sleep(time.Millisecond * 500)
}
}