Go语言:select的典型应用场景之IO超时控制的示例(阻塞方式)

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
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值