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 }