select语句处于阻塞状态直到其中一个channel的收/发操作准备就绪,如果同时有多个channel的收/发操作准备就绪则随机选择其中一个。select最为常见的应用就是IO超时控制,例如HTTP客户端请求,建立TCP连接等等都使用了select来进行超时控制。
非阻塞的方式请见另一篇博客《Go语言:select的典型应用场景之IO超时控制的示例(采用default,非阻塞方式)》
阻塞方式示例:
请求超时设为50毫秒,模拟的IO操作响应延迟为0-100毫秒之间的随机数。多运行几次则部分有响应,其余为超时报错。
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) //用于接收IO响应的通道 respCh := make(chan string) start := time.Now() //另起goroutine执行IO操作 go doIO(respCh) var respData Response var err error //如果先接收到响应则停掉超时计时器 //如果超时计时器先触发,则生成一个error的实例 select { case respData.body = <-respCh: timer.Stop() case <-timer.C: err = errors.New("request timeout") } //记录总耗时 respData.elapse = time.Now().Sub(start) return respData, err } func doIO(respCh chan 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) respCh <- "Hello World" } func main() { resp, err := request(50 * time.Millisecond) if err != nil { fmt.Printf("error: %s\n", err.Error()) return } fmt.Printf("response: body=%s elapse=%s\n", resp.body, resp.elapse) }输出:
[root@dev tutorial]# go run select.go delay=13ms response: body=Hello World elapse=13.231063ms [root@dev tutorial]# go run select.go delay=64ms error: request timeout [root@dev tutorial]# go run select.go delay=67ms error: request timeout [root@dev tutorial]# go run select.go delay=82ms error: request timeout [root@dev tutorial]# go run select.go delay=35ms response: body=Hello World elapse=35.285941ms
Go源码里的例子:
net/http/client.go中函数setRequestCancel的代码节选。这部分代码就是http客户端请求超时控制的核心部分。
go func() { select { case <-initialReqCancel: doCancel() timer.Stop() case <-timer.C: timedOut.setTrue() doCancel() case <-stopTimerCh: timer.Stop() } }()上面的示例也可以把timer改成context.WithDeadline()
func request(timeout time.Duration) (Response, error) { //用于触发超时的上下文 deadline := time.Now().Add(timeout) ctx, cancel := context.WithDeadline(context.Background(), deadline) defer cancel() //用于接收IO响应的通道 respCh := make(chan string) start := time.Now() //另起goroutine执行IO操作 go doIO(respCh) var respData Response var err error //如果先接收到响应则停掉超时计时器 //如果超时计时器先触发,则生成一个error的实例 select { case respData.body = <-respCh: case <-ctx.Done(): err = errors.New("request timeout") } //记录总耗时 respData.elapse = time.Now().Sub(start) return respData, err }