后台运行一个go程序

前言

后台运行在日常开发中比较常用,特别是在部署服务器上,一般都是通过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-daemongithub.com/zh-five/xdaemon 两个库
==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

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

在Go语言中,编写一个守护进程并使其在启动后自动进入后台通常通过以下步骤完成: 1. **设置标准输入、输出和错误**:为了防止进程阻塞,你需要将标准输入、输出和错误重定向到/dev/null或其他无意义的地方,比如 `/dev/tty` 或 `/dev/console`。 ```go import ( "os" "syscall" ) func main() { // 将标准输出和错误重定向到/dev/null os.Stdout = os.NewFile(os.DevNull, "/dev/null") os.Stderr = os.NewFile(os.DevNull, "/dev/null") // 设置为不可中断模式 syscall.Setpgid(0, 0) } ``` 2. **使用`exec.Command`**:你可以使用 `os/exec` 包来执行命令,并指定shell脚本或命令行工具来使进程后台运行。例如,如果你想要让当前进程变为守护进程并退出,可以使用 `&` 符号来分隔命令和进程管理动作。 ```go func daemonize() error { if pid := syscall.Fork(); pid > 0 { // 父进程直接退出 return nil } // 子进程(新守护进程) syscall.CloseOnExec(syscall.Stdin.Fd()) syscall.CloseOnExec(syscall.Stdout.Fd()) syscall.CloseOnExec(syscall.Stderr.Fd()) os.Chdir("/") // 移动到根目录 syscall.Unsetenv("HOME") // 清除HOME环境变量 os.Noop() // 防止信号处理影响子进程 if err := syscall.Exec("/bin/bash", "-c", "while true; do sleep 60; done &"); err != nil { return err } // 这里不应该到达,因为已经执行了新的命令 return nil } func main() { if err := daemonize(); err != nil { log.Fatal(err) } } ``` 在上面的例子中,我们创建了一个无限循环的bash shell脚本,让它一直睡眠并保持守护状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlbertS

常来“玩”啊~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值