谈完TimeoutHandler, 再回到golang timeout,有时虽然我们正常timeout返回, 但并不意味整个groutine就正常返回了。此时调用返回也只是上层返回了, 异步调用的底层逻辑没有办法撤回的。 因为我们没办法cancel掉另一个grouine,只能是groutine主动退出, 主动退出的实现思路大部分是通过传递一个context或者close channel给该groutine, 该groutine监听到退出信号就终止, 但是目前很多调用是不支持接收一个context或close channle作为参数的。
例如下面这段代码:因为在主逻辑中sleep了4s是没有办法中断的, 即时此时request已经返回,但是server端该groutine还是没有被释放, 所以golang timeout这块还是非常容易leak grouine的, 使用的时候需要小心。
package main
import (
"fmt"
"net/http"
"runtime"
"time"
)
func main() {
go func() {
for {
time.Sleep(time.Second)
fmt.Printf("groutine num: %d\n", runtime.NumGoroutine())
}
}()
handleFunc := func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("request %v\n", r.URL)
time.Sleep(4 * time.Second)
_, err := fmt.Fprintln(w, "ok")
if err != nil {
fmt.Printf("write err: %v\n", err)
}
}
err := http.ListenAndServe("localhost:9999", http.TimeoutHandler(http.HandlerFunc(handleFunc), 2*time.Second, "err: timeout"))
if err != nil {
fmt.Printf("%v", err)
}
}
写在最后
golang timeout 简单但是比较繁琐,只有明白其原理才能真正防患于未然