go语言select用法

1 原理

     操作系统中我们会使用select或者epool等实现 I/O 多路复用, Go 语言中关键字select用法与之类似,但go中的select只能等待Channel中的事件。Go 语言中的 select 够让 Goroutine 同时等待多个 Channel 可读或者可写,在多个文件或者 Channel状态改变之前,select 会一直阻塞当前线程或者 Goroutine。

select 是与 switch 相似的控制结构,与 switch 不同的是,select 中虽然也有多个 case,但是这些 case 中的表达式必须都是 Channel 的收发操作。示例如下:

select {
case testErr := <-dialErr:
   require.NoError(t, testErr)
case <-time.After(3 * time.Second):
   require.Fail(t, "DialTcp never returned")
}

     上述两个case中无论哪一个表达式返回都会立刻执行 case 中的代码,当 select 中的两个 case 同时被触发时,会随机执行其中的一个。

 

2 场景

     Select设计初衷是阻塞当前Goroutine等待channel事件,但是某些场合需要非阻塞方式去执行,比如查询状态。后来就增加了default,default的出现,select可以有各种各样的用法,下面就一一列举select的使用场景:

1.    Select中没有case的情况

由于没有case,也就没人唤醒,空的 select 语句会直接阻塞当前 Goroutine,导致 Goroutine 进入无法被唤醒的永久休眠状态。一般情况不应该写出这样的代码。

2.    单一case

如果当前的 select 条件只包含一个 case,那么编译器会将 select 改写成 if 条件语句。会直接阻塞当前 Goroutine,直到该case上的channel事件触发。

3.    多case(无default分支)

常见用法,select在多个case分支上等待,哪个case返回立即执行该case代码,若多个case同时触发,随机执行其中一个,防止单个case出现饿死情况。

4.    多case(有default分支)-非阻塞

常见用法,首先检查所有case 是否满足要求,如果 default 外的case语句都没有事件触发,那么执行 default 里的语句;从而实现非阻塞操作

 

3 用例和坑

     常见用法:

1.    优雅退出

for和select结合使用,Done是外部传入的协程结束标志,default中处理正常的业务逻辑,如果Done事件没有触发,则会一直处理default中的process,并且不会被Done阻塞,如果Done事件触发,此时若正在进行process处理,不会立即退出协程,会等process处理完成后再次进入循环时触发获取Done事件退出。

for {
   select {
   case <-Done:
      return
   default:
      process()
   }
}

2.    为请求设置超长时间

如果为一个阻塞的事件增加超时时间,最好的实现办法就是select加一个time.After的channel,以下以TCP连接为例,显示如何为一个阻塞事件增加超时功能。

go func() {
   dialErr <- tcp.DialTcp("", testPort, lc)
}()
select {
case testErr := <-dialErr:
   require.NoError(t, testErr)
case <-time.After(3 * time.Second):
   require.Fail(t, "DialTcp never returned")
}

3.    select常见的坑

select和for循环结合使用时,需要注意,如果select中有break语句,无法跳出for循环,需要结合goto标签实现。

posted @ 2023-07-07 15:40  易先讯  阅读(79)  评论(0编辑  收藏  举报