附录三

说明

前面几篇文章我们通过测试、分析代码看到了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方法。

results matching ""

    No results matching ""