Golang信號處理實戰(zhàn)
1. 為什么需要信號處理
在類 Unix 系統(tǒng)中,信號(Signal)是一種異步通知機制,內(nèi)核通過它告訴進程發(fā)生了某種事件,比如:
- 終止進程:
SIGTERM(kill 發(fā)送的默認(rèn)信號)、SIGINT(Ctrl+C) - 掛起/恢復(fù):
SIGTSTP(Ctrl+Z) - 重新加載配置:
SIGHUP - 自定義信號:
SIGUSR1、SIGUSR2
如果不處理,進程會使用 默認(rèn)行為(可能直接退出)。
而 os/signal 包讓我們在用戶態(tài)捕獲這些信號,并執(zhí)行自定義邏輯(比如優(yōu)雅退出、保存狀態(tài)、重載配置等)。
2. 核心 API
| 函數(shù) | 功能 | 常見用途 |
|---|---|---|
| Notify(c chan<- os.Signal, sig ...os.Signal) | 將指定信號轉(zhuǎn)發(fā)到 c | 訂閱信號 |
| Stop(c chan<- os.Signal) | 停止向 c 轉(zhuǎn)發(fā)信號 | 取消訂閱 |
| Ignore(sig ...os.Signal) | 忽略信號(不再轉(zhuǎn)發(fā)給程序) | 屏蔽特定信號 |
| Reset(sig ...os.Signal) | 恢復(fù)信號默認(rèn)行為 | 信號處理恢復(fù)默認(rèn) |
| NotifyContext(ctx, sig...) | 返回會在收到信號時自動 cancel 的 Context | 優(yōu)雅退出 |
3. 基本使用
示例:監(jiān)聽SIGINT(Ctrl+C)和SIGTERM(kill)
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
sigChan := make(chan os.Signal, 1)
// 訂閱兩個信號
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
fmt.Println("程序啟動,等待信號...")
sig := <-sigChan // 阻塞等待
fmt.Println("收到信號:", sig)
fmt.Println("執(zhí)行清理邏輯...")
// 這里做關(guān)閉文件、斷開連接等操作
fmt.Println("程序退出")
}
運行:
go run main.go # Ctrl+C 或 kill PID 會觸發(fā)信號
4. 使用NotifyContext優(yōu)雅退出
Go 1.16+ 引入的 NotifyContext 結(jié)合 context 讓信號處理更簡潔。
package main
import (
"context"
"fmt"
"os/signal"
"syscall"
"time"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop()
fmt.Println("程序啟動,等待信號...")
// 模擬業(yè)務(wù)協(xié)程
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("業(yè)務(wù)收到退出信號,清理中...")
time.Sleep(1 * time.Second)
fmt.Println("業(yè)務(wù)清理完成")
return
default:
fmt.Println("業(yè)務(wù)運行中...")
time.Sleep(2 * time.Second)
}
}
}()
<-ctx.Done() // 阻塞,直到信號觸發(fā)
fmt.Println("主程序退出")
}
好處:
- 自動取消 context
- 不用自己建 channel
- 多個 goroutine 可同時感知退出
5. 高級用法
5.1 忽略信號
signal.Ignore(syscall.SIGPIPE) // 忽略管道斷開
5.2 動態(tài)取消訂閱
signal.Stop(sigChan) // 取消 channel 的訂閱
5.3 同時監(jiān)聽多個信號
signal.Notify(sigChan) // 不指定信號時,監(jiān)聽所有信號
不推薦監(jiān)聽全部信號,可能會攔截 SIGKILL、SIGSTOP 等無法處理的信號。
6. 原理機制
簡化版流程:
Notify注冊信號 → 調(diào)用 runtime 的enableSignal(n)。- runtime 捕獲信號后調(diào)用
process()。 process遍歷所有 channel handler,非阻塞發(fā)送信號。Stop時調(diào)用disableSignal(n),等待 runtime 信號隊列清空(signalWaitUntilIdle())。
特點:
- 非阻塞投遞:channel 必須有緩沖,否則可能丟信號。
- 引用計數(shù):多個 channel 可監(jiān)聽同一信號,ref=0 時才會真正停止捕獲。
- bitmask 存儲:handler 用 bit 位記錄關(guān)注的信號,內(nèi)存占用小。
7. 最佳實踐
總是用緩沖 channel
make(chan os.Signal, 1)
避免信號丟失。
優(yōu)雅退出而不是強殺
在SIGTERM里做清理,配合context實現(xiàn)安全收尾。避免監(jiān)聽全部信號
只訂閱需要的信號,避免影響系統(tǒng)默認(rèn)行為。多 goroutine 協(xié)同
用NotifyContext讓所有協(xié)程通過<-ctx.Done()感知退出。容器化部署必備
Docker 默認(rèn)用SIGTERM停止容器,業(yè)務(wù)代碼應(yīng)處理此信號。
8. 實戰(zhàn)案例:優(yōu)雅關(guān)閉 HTTP 服務(wù)器
package main
import (
"context"
"fmt"
"net/http"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8080"}
// 啟動 HTTP 服務(wù)
go func() {
fmt.Println("HTTP 服務(wù)啟動在 :8080")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Println("HTTP 服務(wù)器出錯:", err)
}
}()
// 信號監(jiān)聽
ctx, stop := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer stop()
<-ctx.Done() // 等待信號
fmt.Println("收到退出信號,正在關(guān)閉服務(wù)器...")
// 設(shè)置超時的優(yōu)雅關(guān)閉
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
fmt.Println("服務(wù)器關(guān)閉錯誤:", err)
}
fmt.Println("服務(wù)器已優(yōu)雅退出")
}
這樣寫的好處:
- 支持 Ctrl+C / kill
- 容器化部署時能優(yōu)雅退出
- 確保連接處理完成后再關(guān)閉
到此這篇關(guān)于Golang信號處理實戰(zhàn)的文章就介紹到這了,更多相關(guān)Golang信號處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang判斷chan channel是否關(guān)閉的方法
這篇文章主要介紹了golang判斷chan channel是否關(guān)閉的方法,結(jié)合實例形式對比分析了Go語言判斷chan沒有關(guān)閉的后果及關(guān)閉的方法,需要的朋友可以參考下2016-07-07
Go語言數(shù)據(jù)結(jié)構(gòu)之選擇排序示例詳解
這篇文章主要為大家介紹了Go語言數(shù)據(jù)結(jié)構(gòu)之選擇排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
go-micro集成RabbitMQ實戰(zhàn)和原理詳解
本文主要介紹go-micro使用RabbitMQ收發(fā)數(shù)據(jù)的方法和原理,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05

