select语句当使用default分支时,则采用非阻塞方式从所有收/发操作准备就绪(或者关闭)的channel中则随机选择一个。select最为常见的应用就是IO超时控制,既可以采用阻塞方式也可以采用非阻塞方式来实现,阻塞方式的写法请见另一篇博客《Go语言:select的典型应用场景之IO超时控制的示例(阻塞方式)》。
非阻塞方式示例:
请求超时设为50毫秒,模拟的IO操作响应延迟为0-100毫秒之间的随机数。多运行几次则小于50毫秒延时的有响应,其余为超时报错。
select.go
package main
import (
"errors"
"fmt"
"math/rand"
"time"
)
type Response struct {
body string //响应内容
elapse time.Duration //响应耗时
}
func request(timeout time.Duration) (Response, error) {
//用于触发超时的计时器
timer := time.NewTimer(timeout)
defer timer.Stop()
start := time.Now()
var respData Response
var err error
//同一个协程中执行IO操作
respData.body = doIO()
//如果超时计时器已处在就绪状态,则生成一个error的实例,否则继续往下执行。
select {
case <-timer.C:
err = errors.New("request timeout1")
default:
}
//记录总耗时
respData.elapse = time.Now().Sub(start)
return respData, err
}
func doIO() string{
//随机产生一个[0,100)毫秒的延迟,以模拟IO延时延迟
rand.Seed(time.Now().UnixNano())
delay := time.Duration(rand.Intn(100)) * time.Millisecond
fmt.Printf("delay=%v\n", delay)
time.Sleep(delay)
return "Hello World"
}
func main() {
resp, err := request(50 * time.Millisecond)
if err != nil {
fmt.Printf("error: %s elapse=%s\n", err.Error(), resp.elapse)
return
}
fmt.Printf("response: body=%s elapse=%s\n", resp.body, resp.elapse)
}
输出:
[root@dev tutorial]# go run select.go
delay=84ms
error: request timeout1 elapse=84.174263ms
[root@dev tutorial]# go run select.go
delay=78ms
error: request timeout1 elapse=78.152999ms
[root@dev tutorial]# go run select.go
delay=39ms
response: body=Hello World elapse=39.177298ms
[root@dev tutorial]# go run select.go
delay=19ms
response: body=Hello World elapse=19.196615ms
Go源码里采用非阻塞方式的例子:
net/fd_unix.go的函数
func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, ret error)
代码节选,文件描述符fd等待写操作,如果报错,则以非阻塞方式检查上下文是否超时,是则返回超时错误,否则返回等待写操作的报错。
if err := fd.pfd.WaitWrite(); err != nil {
select {
case <-ctx.Done():
return nil, mapErr(ctx.Err())
default:
}
return nil, err
}