Go1-16 中的新函數 signal-NotifyContext 怎麼用?
大家好,我是 polarisxu。
os/signal 這個包大家可能用的不多。但自從 Go1.8 起,有些人開始使用這個包了,原因是 Go1.8 在 net/http 包新增了一個方法:
func (srv *Server) Shutdown(ctx context.Context) error
有了它就不需要藉助第三方庫實現優雅關閉服務了。具體怎麼做呢?
func main() {
server = http.Server{
Addr: ":8080",
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second * 10)
fmt.Fprint(w, "Hello world!")
})
go server.ListenAndServe()
// 監聽中斷信號(CTRL + C)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
// 重置 os.Interrupt 的默認行爲
signal.Reset(os.Interrupt)
fmt.Println("shutting down gracefully, press Ctrl+C again to force")
// 給程序最多 5 秒時間處理正在服務的請求
timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(timeoutCtx); err != nil {
fmt.Println(err)
}
}
-
這裏利用 os/signal 包監聽 Interrupt 信號;
-
收到該信號後,16 行
<-c
會返回; -
爲了可以再次 CTRL + C 強制退出,通過 Reset 恢復 os.Interrupt 的默認行爲;(這不是必須的)
優雅退出的關鍵:1)新請求進不來;2)已有請求給時間處理完。所以,在接收到信號後,調用 server.Shutdown 方法,阻止新請求進來,同時給 5 秒等待時間,讓已經進來的請求有時間處理。
在 Go1.16 中,os/signal 包新增了一個函數:
func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)
功能和 Notify 類似,但用法上有些不同。上面的例子改用 NotifyContext:
func after() {
server = http.Server{
Addr: ":8080",
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second * 10)
fmt.Fprint(w, "Hello world!")
})
go server.ListenAndServe()
// 監聽中斷信號(CTRL + C)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
<-ctx.Done()
// 重置 os.Interrupt 的默認行爲,類似 signal.Reset
stop()
fmt.Println("shutting down gracefully, press Ctrl+C again to force")
// 給程序最多 5 秒時間處理正在服務的請求
timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(timeoutCtx); err != nil {
fmt.Println(err)
}
}
和上面的寫法有區別,完成的功能一樣的。其實 NotifyContext 的內部就是基於 Notify 實現的:
func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
ctx, cancel := context.WithCancel(parent)
c := &signalCtx{
Context: ctx,
cancel: cancel,
signals: signals,
}
c.ch = make(chan os.Signal, 1)
Notify(c.ch, c.signals...)
if ctx.Err() == nil {
go func() {
select {
case <-c.ch:
c.cancel()
case <-c.Done():
}
}()
}
return c, c.stop
}
只是在返回的 stop 被調用時,會執行 os/signal 包中的 Stop 函數,這個 Stop 函數的功能和 Reset 類似。因此上面 Notify 的例子,Reset 的地方可以改爲 Stop。
從封裝上看,NotifyContext 做的更好。而且,如果在某些需要 Context 的場景下,它把監控系統信號和創建 Context 一步搞定。
NotifyContext 的用法,優雅的關閉服務,你掌握了嗎?希望你實際動手試驗下,啓動服務,通過 curl http://localhost:8080/
訪問,然後按 CTRL + C,看看具體效果。只看不動手,基本知識不是你的。
關於 NotifyContext 函數的文檔可以在這裏查看:https://docs.studygolang.com/pkg/os/signal/#NotifyContext。
我是 polarisxu,北大碩士畢業,曾在 360 等知名互聯網公司工作,10 多年技術研發與架構經驗!2012 年接觸 Go 語言並創建了 Go 語言中文網!著有《Go 語言編程之旅》、開源圖書《Go 語言標準庫》等。
堅持輸出技術(包括 Go、Rust 等技術)、職場心得和創業感悟!歡迎關注「polarisxu」一起成長!也歡迎加我微信好友交流:gopherstudio
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/mnR_wADjyHtyjdkSwcnkSw