攔截信號Golang應用優(yōu)雅關閉的操作方法
Golang不是像C語言的系統(tǒng)級編程語言,但仍提供了以下特性幫助開發(fā)者與底層操作系統(tǒng)進行交互,如信號(singals),os/singals包實現(xiàn)了這些功能。相對于其他語言處理OS信號采用復雜或冗余的方法,Golang內(nèi)置OS包提供了易用的方法處理Unix信號,非常直接、簡單。事實上,類unix系統(tǒng)中通常處理singal是必要的,本文介紹singal概念,并通過示例說明其應用場景。
從示例開始
假設場景:Golang應用在關閉時打印消息:“Thank you for using Golang.” 首先新建main函數(shù),函數(shù)體模擬執(zhí)行一些業(yè)務,直到接收到結(jié)束命令。
func main() {
for {
fmt.Println("Doing Work")
time.Sleep(1 * time.Second)
}
}當運行應用,然后從OS發(fā)送執(zhí)行kill信號(Ctrl + C),可能輸出結(jié)果類似這樣:
Doing Work Doing Work Doing Work Process finished with exit code 2
現(xiàn)在我們希望攔截kill信號并定義處理邏輯:打印必要的關閉信息。
接收信號
首先創(chuàng)建channe接收來自操作系統(tǒng)的命令,os包提供了signal接口處理基于OS規(guī)范信號實現(xiàn)。
killSignal := make(chan os.Signal, 1)
為了通知killSignal,需使用signal包中提供的Notify函數(shù),第一個參數(shù)為Signal類型的channel,下一個參數(shù)接收一組發(fā)給通道的信號列表。
Notify(c chan<- os.Signal, sig ...os.Signal)
我們也可以使用syscall包通知特定命令的信號:
signal.Notify(c chan<- os.Signal, syscall.SIGINT, syscall.SIGTERM)
為了處理信號,我們在main函數(shù)中使用killSignal通道等待interrupt信號,一旦接收到來自OS的命令,則打印結(jié)束消息并接收應用?,F(xiàn)在把處理業(yè)務的循環(huán)代碼移入獨立的goroute中,這里定義一個匿名函數(shù):
go func() {
for {
fmt.Println("Doing Work")
time.Sleep(1 * time.Second)
}
}()因為業(yè)務代碼運行在獨立routine中,main函數(shù)實現(xiàn)等待killSignal信號并在結(jié)束之前打印消息。
<-killSignal
fmt.Println("Thanks for using Golang!")完整代碼
下面把幾個部分組裝在一起,完整代碼如下:
package main
import (
"fmt"
"os"
"os/signal"
"time"
)
func main() {
killSignal := make(chan os.Signal, 1)
signal.Notify(killSignal, os.Interrupt)
go func() {
for {
fmt.Println("Doing Work")
time.Sleep(1 * time.Second)
}
}()
<-killSignal
fmt.Println("Thanks for using Golang!")
}運行程序,看到一直執(zhí)行業(yè)務循環(huán),直到接收到interrupt信號,打印消息并結(jié)束:
Doing Work
Doing Work
Doing Work
Thanks for using Golang!
常見信號
對于命令行程序,當按CTRL+C時,會發(fā)送SIGINT信號,SIGINT是與指定特定信號數(shù)字相關聯(lián)的名稱。信號實際是可以有程序生成的軟件中斷,也是一種異步處理事件機制。通常為了安全起見,信號采用名稱而不是數(shù)值生成。
大多數(shù)信號及其具體動作都是由操作系統(tǒng)定義的,并還在操作系統(tǒng)權限范圍內(nèi)執(zhí)行相應功能。因此能夠處理某些事件并不能讓用戶自由地使用系統(tǒng),有些信號即使生成了也會被操作系統(tǒng)忽略,而由操作系統(tǒng)決定程序允許處理哪些信號。
舉例,SIGKILL 和 SIGSTOP 信號也可以被捕獲、阻塞或忽略。因為這些信號對于保持操作系統(tǒng)功能的"健全"至關重要,在極端條件下為內(nèi)核和root用戶提供了停止進程的方法。與SIGKILL相關聯(lián)的數(shù)字是9,Linux提供了kill命令發(fā)送SIGTERM信號,終止正在運行的程序。可以使用kill -l命令查看完整的支持信號:
圖片
有許多信號可用,但并非所有信號都能被程序處理。像SIGKILL和SIGSTOP這樣的信號既不能被程序調(diào)用,也不能被程序忽略。原因很簡單:它們太重要了,不允許被惡意程序濫用。
系統(tǒng)信號分為同步信號和異步信號。其中同步信號是程序執(zhí)行中的錯誤觸發(fā)的信號,如SIGBUS, SIGFPE, SIGSEGV,在 Golang 程序中,同步信號通常會被轉(zhuǎn)換為 runtime panic。同步信號需要事件及事件時間,相反異步信號不需要于時鐘同步,異步信號是系統(tǒng)內(nèi)核或其它程序發(fā)送的信號。舉例,SIGHUP表示它控制的終端被關閉,常用的是當按CTRL+C時,會發(fā)送SIGINT信號。當SIGINT信號有鍵盤產(chǎn)生時,實際上不是終止應用程序,而是生成特定鍵盤中斷,并且處理該中斷的程序開始執(zhí)行,其缺省行為時關閉應用程序,但我們可以覆蓋缺省行為,寫我們自己的業(yè)務處理邏輯。
事實上許多信號處理都可以被覆蓋,Go開發(fā)者可以編寫自己的處理程序來自定義信號處理行為。然而,除非有非常好的理由,否則改變信號的默認行為并不是明智的想法。這里引用列舉常用linux信號:
SIGHUP: ‘HUP’ = ‘hung up’. This signal is generated when a controlling process dies or hangs up detected on the controlling terminal.
SIGINT: When a process is interrupted from keyboard by pressing CTRL+C
SIGQUIT: Quit from keyboard
SIGILL: Illegal instruction. A synonym for SIGPWR() – power failure
SIGABRT: Program calls the abort() function – an emergency stop.
SIGBUS: Bad memory access. Attempt was made to access memory inappropriately
SIGFPE: FPE = Floating point exception
SIGKILL: The kill signal. The process was explicitly killed.
SIGUSR1: This signal is open for programmers to write a custom behavior.
SIGSEGV: Invalid memory reference. In C when we try to access memory beyond array limit, this signal is generated.
SIGUSR2: This signal is open for programmers to write a custom behavior.
SIGPIPE: This signals us open for programmers to write a custom behavior.
SIGALRM: Process requested a wake up call by the operating system such as by calling the alarm() function.
SIGTERM: A process is killed
處理多個信號
下面再看一個示例,較上面的示例增加了對SIGTERM信號的處理:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func handler(signal os.Signal) {
if signal == syscall.SIGTERM {
fmt.Println("Got kill signal. ")
fmt.Println("Program will terminate now.")
os.Exit(0)
} else if signal == syscall.SIGINT {
fmt.Println("Got CTRL+C signal")
fmt.Println("Closing.")
os.Exit(0)
} else {
fmt.Println("Ignoring signal: ", signal)
}
}
func main() {
sigchnl := make(chan os.Signal, 1)
signal.Notify(sigchnl)
// signal.Notify(sigchnl, os.Interrupt, syscall.SIGTERM)
exitchnl := make(chan int)
go func() {
for {
s := <-sigchnl
handler(s)
}
}()
exitcode := <-exitchnl
os.Exit(exitcode)
}
首先創(chuàng)建通道接收響應信號,我們也可以指定信號類型:signal.Notify(sigchnl, os.Interrupt, syscall.SIGTERM),否則會接收所有信號,包括命令窗口改變大小信號。然后在handle函數(shù)判斷信號類型并分別進行處理。
程序運行后,linux可以通過ps -h 查看go程序,然后執(zhí)行kill命令。
NotifyContext示例
Go.1.16提供了signal.NotifyContext函數(shù),可以實現(xiàn)更優(yōu)雅的關閉功能。下面示例實現(xiàn)簡單web服務器,示例handle需要10s處理時間,如果正在運行的web服務,客戶端通過postman或cURL發(fā)送請求,然后在服務端立刻通過ctrl+C發(fā)送終止信號。
我們希望看到服務器在終止之前通過響應終止請求而優(yōu)雅地關閉。如果關閉時間過長,可以發(fā)送另一個中斷信號立即退出,或者暫停將在5秒后開始。
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
)
var (
server http.Server
)
func main() {
// Create context that listens for the interrupt signal from the OS.
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
server = http.Server{
Addr: ":8080",
}
// Perform application startup.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Second * 10)
fmt.Fprint(w, "Hello world!")
})
// Listen on a different Goroutine so the application doesn't stop here.
go server.ListenAndServe()
// Listen for the interrupt signal.
<-ctx.Done()
// Restore default behavior on the interrupt signal and notify user of shutdown.
stop()
fmt.Println("shutting down gracefully, press Ctrl+C again to force")
// Perform application shutdown with a maximum timeout of 5 seconds.
timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(timeoutCtx); err != nil {
fmt.Println(err)
}
}
總結(jié)
本文介紹了信號的概念及常用信號,并給出了應用廣泛的幾個示例,例如優(yōu)雅地關閉應用服務、在命令行應用中接收終止命令。
到此這篇關于攔截信號Golang應用優(yōu)雅關閉的文章就介紹到這了,更多相關Golang關閉內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
5個可以在Golang中優(yōu)化代碼以提高性能的技巧分享
作為一名軟件工程師,確保你的代碼高效且性能良好是非常重要的。本文主要和大家分享5個可以在Golang中優(yōu)化代碼以提高性能的技巧,希望對大家有所幫助2023-03-03
Go語言操作數(shù)據(jù)庫及其常規(guī)操作的示例代碼
這篇文章主要介紹了Go語言操作數(shù)據(jù)庫及其常規(guī)操作的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04

