后台运行一个go程序

前言

后台运行在日常开发中比较常用,特别是在部署服务器上,一般都是通过ssh连接到服务器,然后启动后台运行程序,如果程序不支持后台运行,那么当终端断开时程序也就退出了,所以掌握常用的后台运行方式还是比较有用的。

概念

提到后台运行,通常会想到 daemon 模式,日常开发时也常常混着说,不过通过查询资料时发现,这两个概念还有些区别:

后台运行:是指进程在操作系统中非显示运行,未关联到任何命令行终端或程序界面,这种方式运行的进程则被称为后台进程。

daemon模式:也叫守护进程,它首先是后台运行,然后它还有守护的职责,若异常退出,可以自动重启服务程序。

所以说 daemon 不仅要时候后台运行,还有守护进程职责,像Windows 和 Linux 中的各种服务,比如MySQL、防火墙、SSH服务等都是后台运行的进程。

常用方式

很多产品会部署在linux服务器上,所以相比较而言,后台运行在linux上更常用,而 nohup&setsid 等命令就基本上可以达到后台运行的目的,之前写过一篇总结 《linux环境下运行程序常用的nohup和&的区别》,可以简单回忆下:

  • nohup 是no hang up的缩写,就是不挂断的意思,忽略SIGHUP信号,在关闭命令终端后程序依旧运行
  • & 是只后台运行,即忽略SIGINT信号,也就是按Ctrl+C不会终止程序,但是关闭命令行终端程序终止

setsid 是新学到的命令,使用起来也非常的简单,只需要加在待执行命令的前面即可:

1
[root@VM-0-3-centos ~]# setsid ping www.baidu.com > out.log

此时关闭当前终端,重新打开另一终端会发现 ping 的进程,同时文件 out.log 文件也一直在更新

1
2
3
4
5
6
7
8
[root@VM-0-3-centos ~]# ps -ef | grep ping
root 1692 1 0 23:36 ? 00:00:00 ping www.baidu.com
root 1707 1279 0 23:36 pts/1 00:00:00 grep --color=auto ping
[root@VM-0-3-centos ~]# tail -f out.log
64 bytes from 110.242.68.3 (110.242.68.3): icmp_seq=8 ttl=251 time=9.38 ms
64 bytes from 110.242.68.3 (110.242.68.3): icmp_seq=9 ttl=251 time=9.36 ms
64 bytes from 110.242.68.3 (110.242.68.3): icmp_seq=10 ttl=251 time=9.35 ms
64 bytes from 110.242.68.3 (110.242.68.3): icmp_seq=11 ttl=251 time=9.37 ms

小技巧

& 不能让进程永久在后台执行,但是如果在命令前后加上()括起来,一样可以实现nohup ..&的功能,命令就能永久在后台执行了。

1
2
3
4
5

[root@VM-0-3-centos ~]# (ping www.baidu.com > t.log &)
[root@VM-0-3-centos ~]# ps -ef | grep ping
root 3410 1 0 23:44 pts/0 00:00:00 ping www.baidu.com
root 3450 1279 0 23:44 pts/1 00:00:00 grep --color=auto ping

代码级别实现

虽然上面的方式很方便,但毕竟只能在 linux 上使用,如果可以通过修改 go 代码在 windows 和 linux 上都实现后台运行那再好不过了,很幸运查到一个go的库 github.com/codyguo/godaemon,使用起来非常方便,只需要在代码中引入这个库,然后启动程序是加入 -d 参数就可以后台运行了

1
2
3
import (
_ "github.com/codyguo/godaemon"
)

不过我在使用的过程中发现两个问题,一个是传递的后续参数会莫名消失,另一个是好像关闭终端会导致程序退出,所以我打算看看源码,结果发现源码就只有几行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package godaemon

import (
"flag"
"fmt"
"os"
"os/exec"
)

func init() {
goDaemon := flag.Bool("d", false, "run app as a daemon with -d=true.")
flag.Parse()

if *goDaemon {
cmd := (os.Args[0], flag.Args()...)
if err := cmd.Start(); err != nil {
fmt.Printf("start %s failed, error: %v\n", os.Args[0], err)
os.Exit(1)
}
fmt.Printf("%s [PID] %d running...\n", os.Args[0], cmd.Process.Pid)
os.Exit(0)
}
}

这些就是源码的全部了,是不是很吃惊,其实弄懂原理就很好明白了,其中利用了 exec.Command 函数,因为go 中没有 fork 的便利实现,所以可以利用 exec.Command 启动新的进程,这样新启动的进程在当前进程退出后就被系统进程接管了,只不过它处理的参数有点问题,flag.Args()会把所有 - 开头的参数都消耗掉,自己按需实现就可以了

另一个关闭终端会导致程序退出的问题,可以在传入 -d 参数的情况下调用 signal.Ignore(syscall.SIGHUP) 忽略掉 SIGHUP 信号即可

总结

  • 在操作系统中非显示运行,未关联到任何命令行终端或程序界面的进程则被称为后台进程
  • linux环境下常用来后台运行程序的命令有 hohup&setsid
  • github.com/codyguo/godaemon 是一个极简的后台运行可用库,仅添加修改一行代码
  • 若想更丰富的功能可以参考 github.com/sevlyar/go-daemongithub.com/zh-five/xdaemon 两个库
==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

人生若只如初见,何事秋风悲画扇。等闲变却故人心,却道故人心易变~

2022-10-24 00:04:29

Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客