前言
后台运行在日常开发中比较常用,特别是在部署服务器上,一般都是通过ssh连接到服务器,然后启动后台运行程序,如果程序不支持后台运行,那么当终端断开时程序也就退出了,所以掌握常用的后台运行方式还是比较有用的。
概念
提到后台运行,通常会想到 daemon
模式,日常开发时也常常混着说,不过通过查询资料时发现,这两个概念还有些区别:
后台运行:是指进程在操作系统中非显示运行,未关联到任何命令行终端或程序界面,这种方式运行的进程则被称为后台进程。
daemon模式:也叫守护进程,它首先是后台运行,然后它还有守护的职责,若异常退出,可以自动重启服务程序。
所以说 daemon 不仅要时候后台运行,还有守护进程职责,像Windows 和 Linux 中的各种服务,比如MySQL、防火墙、SSH服务等都是后台运行的进程。
常用方式
很多产品会部署在linux服务器上,所以相比较而言,后台运行在linux上更常用,而 nohup
、&
、setsid
等命令就基本上可以达到后台运行的目的,之前写过一篇总结 《linux环境下运行程序常用的nohup和&的区别》,可以简单回忆下:
nohup
是no hang up的缩写,就是不挂断的意思,忽略SIGHUP信号,在关闭命令终端后程序依旧运行&
是只后台运行,即忽略SIGINT信号,也就是按Ctrl+C不会终止程序,但是关闭命令行终端程序终止
而 setsid
是新学到的命令,使用起来也非常的简单,只需要加在待执行命令的前面即可:
[root@VM-0-3-centos ~]# setsid ping www.baidu.com > out.log
此时关闭当前终端,重新打开另一终端会发现 ping
的进程,同时文件 out.log
文件也一直在更新
[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 …&的功能,命令就能永久在后台执行了。
[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
参数就可以后台运行了
import (
_ "github.com/codyguo/godaemon"
)
不过我在使用的过程中发现两个问题,一个是传递的后续参数会莫名消失,另一个是好像关闭终端会导致程序退出,所以我打算看看源码,结果发现源码就只有几行:
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-daemon
和github.com/zh-five/xdaemon
两个库
人生若只如初见,何事秋风悲画扇。等闲变却故人心,却道故人心易变~