附录三
说明
前面几篇文章我们通过测试、分析代码看到了golang网络超时的实现机制。
分析发现,golang内部实现的超时与我们理解的os的超时有着本质不同。golang的超时是针对一次读为其设置了read deadline,而且是一个绝对时间:一旦时间到期,继续读该网络连接便会出现io timeout的错误。
这里我们来简单总结下,如果我们要实现超时有哪几种方法。
方案一:使用go接口
本章中仔细描述了go的超时接口、实现方案以及可能的坑。那如果要使用go自带的超时功能,我们该如何用呢?
如果我们要使用golang的超时的话,必须要小心谨慎了:在每次读之前我们都要去设置下次超时时间,否则会出现一次读很快就出现超时错误。
即以下两个API的调用总是出现在一起的:
SetDeadline(t time.Time) error
Read()
这样可以避免我们前面说过的问题:每次重设超时能将老的超时定时器给删掉。如果老的超时定时器回调函数已经触发,也不会产生任何影响。因为此时内部的seq++了,老的超时定时器的回调函数触发时能判断自己已经过时了,于是就什么也不做了。
方案二:自己管理超时
该方案采用类似golang http库的实现机制,在http.Client的Get方法中,可以为其设置一个超时时间,用法如下:
// 设置Get的超时时间为5s
client = http.Client{Timeout: 5 * time.Second}
client.Get(url)
我们看看http.Client.Get()是如何实现超时的:
func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.doFollowingRedirects(req, shouldRedirectGet)
}
func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bool) (resp *Response, err error) {
......
if c.Timeout > 0 {
wasCanceled = func() bool { return atomic.LoadInt32(&atomicWasCanceled) != 0 }
timer = time.AfterFunc(c.Timeout, func() {
atomic.StoreInt32(&atomicWasCanceled, 1)
reqmu.Lock()
defer reqmu.Unlock()
tr.CancelRequest(req)
})
}
}
可以看到http库压根就没利用golang socket的超时,而是在上层实现了自己的超时机制:使用time.AfterFunc()方法在超时时间后执行了一个取消request方法。