go基础库之正常关闭应用程序

服务和守护程序是运行很长时间(通常是几天甚至几周)的程序。这些长时间运行的程序通常在开始时分配资源(数据库连接,网络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读取的数据被阻塞,因此程序一直运行,直到信号通过通道发送。sigChanNotify函数发送信号的通道。

程序的主代码在一个新的goroutine中运行。这样,当sigChan上的主函数被阻塞时,工作将继续进行。一旦操作系统的信号被发送到进程中,sigChan将接收信号并且执行从sigChan通道读取的代码。这段代码可以看作是清理部分。

注意,终端输出包含的最终日志,应用程序释放所有资源,这是清理部分的一部分。