go为channel设置超时的正确姿势 2019-01-02 17:45:09 golang没有提供直接的超时处理机制,可以利用select机制来解决超时问题,先上个示例: ```go func timeoutFunc() { // 首先,实现并执行一个匿名的超时等待函数 timeout := make(chan bool, 1) go func() { time.Sleep(1e9) //等待1秒钟 timeout <- true }() // 然后,我们把timeout这个channel利用起来 select { case <- ch: // 从ch中读到数据 case <- timeout: // 一直没有从ch中读取到数据,但从timeout中读取到数据 } } ``` 这个示例,看起来能实现效果,假如我们的场景是: 1. timeout设置的时间比较长,可能1h 2. timeoutFunc会被频繁调用(如http服务,每个请求调用一次) 3. ch内很快就有数据,一般不会达到超时时间 在回头看下这个代码,不难发现问题,没到超时时间这个timeoutFunc函数就返回了,但是内部的: ```go go func() { time.Sleep(1e9 * 60 * 60) // 1h timeout <- true }() ``` 这个goroutine不到超时时间是不会退出的,频繁调用timeoutFunc,就会导致大量的goroutine在Sleep,虽然goroutine很轻量,但依然会消耗内存,这样的代码,在高频访问的场景下,你会发现自己的服务内存消耗在一路飙升。 正确的处理姿势应该是在`ch`读取完内容后,将Sleep的goroutine结束掉,改进后的代码如下: ```go // 示例代码,部分变量请自行脑补,😓 func timeoutFunc() { timeout := make(chan bool, 1) isRequestEnd := make(chan bool, 1) timer := time.NewTimer(waitingTimeout) go func() { select { case <-timer.C: timeout <- true case <-isRequestEnd: } }() select { case <-ch: isRequestEnd <- true // 防止timer长时间堆积消耗内存 case <-timeout: } } ``` 非特殊说明,均为原创,原创文章,未经允许谢绝转载。 原始链接:go为channel设置超时的正确姿势 赏 Prev 记一个困扰我们前端同学很长时间的IE下无法设置cookie的问题 Next Spark之BloomFilter有趣的bitwise运算