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)
 }
}

優雅退出的關鍵: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