由于近期项目需要,使用go预研开发边缘智能程序,因此就想着通过golang实现进程守护及进程监控的功能,在golang程序中启动子进程,有很好的封装exec.Command,导入包为os/exec,本文主要以此种方式实现,包含以下两种实现方式。
此方式是把相关代码集成到我们需要监控的进程代码中,通过命令行参数来判断是否启用守护进程,其代码如下所示:
test.go
package test
import (
"flag"
"fmt"
"os"
"os/exec"
"time"
)
func main() {
daemon := flag.Bool("deamon", false, "run in daemon and forever")
flag.Parse()
//判断是否以守护进程的方式启动
if *daemon {
runbydeamon(os.Args) //执行deamon,由其启动主程序
}
//业务主程序代码
fmt.Println("start test Service")
}
//以deamon的方式启动程序,并监控进程
func runbydeamon(args []string) {
fmt.Printf("PID: %d PPID: %d ARG: %s\n", os.Getpid(), os.Getppid(), os.Args)
//去掉-deamon运行参数,启动主程序
for i := 0; i < len(args); {
if args[i] == "-deamon" && i != len(args)-1 {
args = append(args[:i], args[i+1:]...)
} else if args[i] == "-deamon" && i == len(args)-1 {
args = args[:i]
} else {
i++
}
}
//启动子进程,去掉-deamon参数后,执行程序,由于子程序无deamon参数,因此会直接执行业务代码
for {
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()//开启子进程
if err != nil {
fmt.Fprintf(os.Stderr, "start oasis erir [-] Error: %s\n", err)
return
}
fmt.Println("run oasis bydeamon,pid=", cmd.Process.Pid, ",ppid=", os.Getpid(), "args=", args, "time=", time.Now())
cmd.Wait() //阻塞等待进程退出
}
}
通过go buid编写以后,执行"test -deamon"后,由于带有代码会启动两个test进程,第一次启动时由于带有参数"-deamon",因此会调用runbydeamon,此函数会去掉-deamon参数,再次以子进程的方式执行"test",通过cmd.start()启动子进程,子进程由于不带"-deamon",会直接进入业务代码实现,这里会打印:
start test Service
,守护进程有一个for循环,一直在等待子进程退出,如果子进程异常退出,守护进程会再次把子进程进程拉起来并等待。
上文实现方式,与业务代码耦合,启动时只需要带有一个参数即可启动守护进程和业务进程,启动比较简单,但是代码与业务耦合在一起,这种写法使得没法复用守护进程,其实守护进程与业务进程没有任何关系,大可提取出来,作为通用的守护进程程序,支持任务进程的后台启动和监控,代码如下:
deamon.go
package deamon
import (
"flag"
"fmt"
"os"
"os/exec"
"time"
)
func main() {
pcmd := flag.String("cmd", "", "run cmd in daemon and forever")
flag.Parse()
runbydeamon(*pcmd) //执行deamon,由其启动主程序
}
//以deamon的方式启动程序,并监控进程
func runbydeamon(args string) {
fmt.Printf("PID: %d PPID: %d ARG: %s\n", os.Getpid(), os.Getppid(), os.Args)
if cmd==""{
fmt.Println("cmd is null")
os.Exit(0)
}
//启动子进程,既是去掉-deamon参数后,执行程序,由于子程序无deamon参数,因此会直接执行业务代码
for {
cmd := exec.Command(args[1], args[2:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()//开启子进程
if err != nil {
fmt.Fprintf(os.Stderr, "start oasis erir [-] Error: %s\n", err)
return
}
fmt.Println("run oasis bydeamon,pid=", cmd.Process.Pid, ",ppid=", os.Getpid(), "args=", args, "time=", time.Now())
cmd.Wait() //阻塞等待进程退出
}
}
test.go
package test
import (
"flag"
"fmt"
)
func main() {
fmt.Println("start test Service")
}
以守护进程的方式启动进程命令:
deamon -cmd “test”
如果执行test要带命令行,可直接作为deamon的命令行传递给test:
deamon -cmd “test” -c “config.yaml”
有关运行参数flag定义,可根据需要自行定义,这里仅是一个例子