服务和守护程序是运行很长时间(通常是几天甚至几周)的程序。这些长时间运行的程序通常在开始时分配资源(数据库连接,网络sock)并保留这些资源,只要它们存在,就不会释放。如果此类进程被终止并且未正确处理关闭,则可能发生资源泄漏。为了避免这种行为,应该实现所谓的正常关闭。
在这种情况下,优雅 意味着应用程序捕获终止信号(如果可能),并尝试在终止之前清理和释放分配的资源。本篇将向你展示如何实现正常关闭。
正常关闭应用程序
Golang 版本
1.12.1
前言
服务和守护程序是运行很长时间(通常是几天甚至几周)的程序。这些长时间运行的程序通常在开始时分配资源(数据库连接,网络sock)并保留这些资源,只要它们存在,就不会释放。如果此类进程被终止并且未正确处理关闭,则可能发生资源泄漏。为了避免这种行为,应该实现所谓的正常关闭。
在这种情况下,优雅 意味着应用程序捕获终止信号(如果可能),并尝试在终止之前清理和释放分配的资源。本篇将向你展示如何实现正常关闭。
实现
package main
import (
"fmt"
"io"
"log"
"os"
"os/signal"
"syscall"
"time"
)
var writer *os.File
func main() {
// 该文件作为要写入的日志文件打开。这样我们就代表了资源分配。
var err error
writer, err = os.OpenFile(fmt.Sprintf("test_%d.log",
time.Now().Unix()), os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
panic(err)
}
// 代码独立地在goroutine中运行。因此,如果程序从外部终止,我们需要通过closeChan让goroutine知道
closeChan := make(chan bool)
go func() {
for {
time.Sleep(time.Second)
select {
case <-closeChan:
log.Println("Goroutine 关闭...")
return
default:
log.Println("写入日志")
io.WriteString(writer, fmt.Sprintf("记录写入 %s\n", time.Now().String()))
}
}
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGINT)
// 堵塞从sigChan读取Notify函数发送的信号
<-sigChan
// 收到信号后,通道读取后面的所有代码都可以视为清理。清洁部分
close(closeChan)
releaseAllResources()
fmt.Println("应用程序正常关闭")
}
func releaseAllResources() {
io.WriteString(writer, "应用程序释放所有资源 \n")
writer.Close()
}
$ go run main.go
2019/05/22 20:16:00 写入日志
2019/05/22 20:16:01 写入日志
2019/05/22 20:16:02 写入日志
^C应用程序正常关闭
$ cat test_1558509359.log
记录写入 2019-05-22 20:16:00.2506194 +0800 CST m=+1.017343401
记录写入 2019-05-22 20:16:01.2513848 +0800 CST m=+2.018108801
记录写入 2019-05-22 20:16:02.2527773 +0800 CST m=+3.019501301
应用程序释放所有资源
原理
从sigChan
读取的数据被阻塞,因此程序一直运行,直到信号通过通道发送。sigChan
是Notify
函数发送信号的通道。
程序的主代码在一个新的goroutine
中运行。这样,当sigChan
上的主函数被阻塞时,工作将继续进行。一旦操作系统的信号被发送到进程中,sigChan
将接收信号并且执行从sigChan
通道读取的代码。这段代码可以看作是清理部分。
注意,终端输出包含的最终日志,应用程序释放所有资源,这是清理部分的一部分。