Golang 并发 - Select

Select 的作用

select 的用法有点类似 switch 语句,但 select 不会有输入值而且只用于信道操作。select 用于从多个发送或接收信道操作中进行选择,语句会阻塞直到其中有信道可以操作,如果有多个信道可以操作,会随机选择其中一个 case 执行。
看下例子:

func service1(ch chan string) {
    time.Sleep(2 * time.Second)
    ch <- "from service1"
}
func service2(ch chan string) {
    time.Sleep(1 * time.Second)
    ch <- "from service2"
 }
 
func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go service1(ch1)
    go service2(ch2)

    select {       // 会发送阻塞
    case s1 := <-ch1:
        fmt.Println(s1)
    case s2 := <-ch2:
        fmt.Println(s2)
    }
}

输出:from service2
上面的例子执行到 select 语句的时候回发生阻塞,main 协程等待一个 case 操作可执行,很明显是 service2 先准备好读取的数据(休眠 1s),所以输出 from service2。
看下在两种操作都准备好的情况:

func service1(ch chan string) {
    //time.Sleep(2 * time.Second)
    ch <- "from service1"
}
func service2(ch chan string) {
    //time.Sleep(1 * time.Second)
    ch <- "from service2"
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go service1(ch1)
    go service2(ch2)

    time.Sleep(2*time.Second)
    select {
    case s1 := <-ch1:
        fmt.Println(s1)
    case s2 := <-ch2:
        fmt.Println(s2)
    }
}

我们把函数里的延时注释掉,主函数 select 之前加 2s 的延时以等待两个信道的数据准备好,select 会随机选取其中一个 case 执行,所以输出也是随机的。

default case

与 switch 语句类似,select 也有 default case,是的 select 语句不在阻塞,如果其他信道操作还没有准备好,将会直接执行 default 分支。

 1func service1(ch chan string) {
 2    ch <- "from service1"
 3}
 4func service2(ch chan string) {
 5    ch <- "from service2"
 6}
 7
 8func main() {
 9    ch1 := make(chan string)
10    ch2 := make(chan string)
11    go service1(ch1)
12    go service2(ch2)
13
14    select {         // ch1 ch2 都还没有准备好,直接执行 default 分支 
15    case s1 := <-ch1:
16        fmt.Println(s1)
17    case s2 := <-ch2:
18        fmt.Println(s2)
19    default:
20        fmt.Println("no case ok")
21    }
22}

输出:no case ok
执行到 select 语句的时候,由于信道 ch1、ch2 都没准备好,直接执行 default 语句。

func service1(ch chan string) {
    ch <- "from service1"
}
func service2(ch chan string) {
    ch <- "from service2"
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go service1(ch1)
    go service2(ch2)

    time.Sleep(time.Second)   // 延时 1s,等待 ch1 ch2 准备就绪

    select {
    case s1 := <-ch1:
        fmt.Println(s1)
    case s2 := <-ch2:
        fmt.Println(s2)
    default:
        fmt.Println("no case ok")
    }
}

在 select 语句之前加了 1s 延时,等待 ch1 ch2 准备就绪。因为两个通道都准备好了,所以不会走 default 语句。随机输出 from service1 或 from service2。

nil channel

信道的默认值是 nil,不能对 nil 信道进行读写操作。看下面的例子

func service1(ch chan string) {
    ch <- "from service1"
}

func main() {

    var ch chan string
    go service1(ch)
    select {
    case str := <-ch:
        fmt.Println(str)
    }
}

报错:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:

goroutine 18 [chan send (nil chan)]:

有两个错误的地方需要注意:
[select (no cases)] case 分支中如果信道是 nil,该分支就会被忽略,那么上面就变成空 select{} 语句,阻塞主协程,调度 service1 协程,在 nil 信道上操作,便报[chan send (nil chan)] 错误。可以使用上面的 default case 避免发生这样的错误。

func service1(ch chan string) {
    ch <- "from service1"
}

func main() {

    var ch chan string
    go service1(ch)
    select {
    case str := <-ch:
        fmt.Println(str)
    default:
        fmt.Println("I am default")
    }
}

输出:I am default

添加超时时间

有时候,我们不希望立即执行 default 语句,而是希望等待一段时间,若这个时间段内还没有可操作的信道,则执行规定的语句。可以在 case 语句后面设置超时时间。

func service1(ch chan string) {
    time.Sleep(5 * time.Second)
    ch <- "from service1"
}
func service2(ch chan string) {
    time.Sleep(3 * time.Second)
    ch <- "from service2"
}

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go service1(ch1)
    go service2(ch2)

    select {       // 会发送阻塞
    case s1 := <-ch1:
        fmt.Println(s1)
    case s2 := <-ch2:
        fmt.Println(s2)
    case <-time.After(2*time.Second):     // 等待 2s
        fmt.Println("no case ok")
    }
}

输出:no case ok
在第三个 case 语句中设置了 2s 的超时时间,这 2s 内如果其他可操作的信道,便会执行该 case。

空 select

package main

func main() {  
    select {}
}

我们知道 select 语句会发生阻塞,直到有 case 可以操作。但是空 select 语句没有 case 分支,所以便一直阻塞引起死锁。
报错:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值