脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - Golang守护进程用法示例分析

Golang守护进程用法示例分析

2023-05-17 13:04dkjhl Golang

这篇文章主要介绍了Golang守护进程用法示例,创建守护进程首先要了解go语言如何实现创建进程,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习吧

前言

golang实现守护进程,包含功能:

1. 守护进程只创建一次

2. 平滑创建业务进程

3. 业务进程挂起,守护进程能监听,并重启新启业务进程

4. 守护进程退出,也能保证业务进程退出

5. 业务进程≈子进程

6. 不影响业务进程逻辑

7. 以Linux平台为主,其他平台暂时没有实施条件

分析

上一篇博文讨论过如何以脚本的形式创建守护进程,这篇讨论如何以纯golang脚本实现守护进程的功能

  • 在 Unix 中,创建一个进程,通过系统调用 fork 实现(及其一些变种,如 vfork、clone)。
  • 在 Go 语言中,Linux 下创建进程使用的系统调用是 clone 。

在 C 语言中,通常会用到 2 种创建进程方法:

fork

?
1
2
3
4
pid = fork();
//pid > 0 父进程
//pid = 0 子进程
//pid < 0 出错

程序会从 fork 处一分为二,父进程返回值大于0,并继续运行;子进程获得父进程的栈、数据段、堆和执行文本段的拷贝,返回值等于0,并向下继续运行。通过 fork 返回值可轻松判断当前处于父进程还是子进程。

execve

?
1
2
3
4
execve(pathname, argv, envp);
//pathname 可执行文件路径
//argv 参数列表
//envp 环境变量列表

execve 为加载一个新程序到当前进程的内存,这将丢弃现存的程序文本段,并为新程序重新创建栈、数据段以及堆。通常将这一动作称为执行一个新程序。

在 Go 语言中,创建进程方法主要有 3 种:

exec.Command

?
1
2
3
4
5
6
7
8
9
10
11
12
//判 断当其是否是子进程,当父进程return之后,子进程会被 系统1 号进程接管
if os.Getppid() != 1 {
    // 将命令行参数中执行文件路径转换成可用路径
    filePath, _ := filepath.Abs(os.Args[0])
    cmd := exec.Command(filePath, os.Args[1:]...)
    // 将其他命令传入生成出的进程
    cmd.Stdin = os.Stdin // 给新进程设置文件描述符,可以重定向到文件中
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Start() // 开始执行新进程,不等待新进程退出
    os.Exit(0)
}

os.StartProcess

?
1
2
3
4
5
if os.Getppid()!=1{  
    args:=append([]string{filePath},os.Args[1:]...)
    os.StartProcess(filePath,args,&os.ProcAttr{Files:[]*os.File{os.Stdin,os.Stdout,os.Stderr}})
    os.Exit(0)
}

syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)

?
1
2
3
4
5
pid, _, sysErr := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if sysErr != 0 {
    Utils.LogErr(sysErr)
    os.Exit(0)
}

方法1和方法2通过 os.Getppid()!=1进行判断是否子进程,默认父进程退出之后,子进程会被1号进程接管。

但据Ubuntu Desktop 本地测试,接管孤儿进程的并不是1号进程,因此考虑到程序稳定性和兼容性,不能够以 ppid 作为判断父子进程的依据。

方法3直接进行了系统调用,虽然可以通过 pid 进行判断父子进程,但该方法过于底层。

综上,以exec.Command方式,通过控制参数实现守护进程

实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
func main() {
    // ------------------------ 守护进程 start ------------------------
    basePath, _ := os.Getwd()
    baseDir := filepath.Dir(basePath)
    fmt.Println(fmt.Sprintf("basePath is %s and baseDir is %s", basePath, baseDir))
    // step1
    // 创建监听退出chan
    c := make(chan os.Signal)
    // 监听指定信号 ctrl+c kill
    signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
    go func() {
        for s := range c {
            switch s {
            case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
                utils.StopBusinessProcess(fmt.Sprintf("go_start | grep business"))
                os.Exit(0)
            default:
                fmt.Println("test stop others...")
            }
        }
    }()
    fmt.Println(fmt.Sprintf("os.args is %v", os.Args))
    join := strings.Join(os.Args, "")
    // step2
    if !strings.Contains(join, "-daemon") {
        fmt.Println("enter daemon branch...")
        isE, ierr := utils.CheckProRunning("go_start | grep daemon")
        if ierr != nil {
            fmt.Println("check daemon process failed, " + ierr.Error())
            return
        }
        if isE {
            fmt.Println("daemon process exist!")
        } else {
            fmt.Println("start daemon process...")
            // 启动守护进程
            cmd := exec.Command(os.Args[0], "-c", os.Args[2], "-d", os.Args[4], "-e", os.Args[6], "-daemon")
            cmd.Stdin = os.Stdin
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr
            strerr := cmd.Start()
            if strerr != nil {
                fmt.Println("start daemon process fail," + strerr.Error())
                return
            }
            fmt.Println("start daemon process success!")
            time.Sleep(time.Second * 2)
            daePid := cmd.Process.Pid
            isDae, daeErr := utils.CheckProRunning("go_start | grep daemon")
            if daeErr != nil {
                fmt.Println("check daemon process failed, " + daeErr.Error())
                return
            }
            if isDae {
                fmt.Println(fmt.Sprintf("start daemon process success, pid is %d", daePid))
                return
            } else {
                fmt.Println("warning! start business process fail...")
            }
        }
    }
    // step3
    join = strings.Join(os.Args, "")
    if strings.Contains(join, "-daemon") {
        fmt.Println("enter business branch...")
        for {
            exist, checkerr := utils.CheckProRunning("go_start | grep business")
            if checkerr != nil {
                fmt.Println("check business failed, " + checkerr.Error())
                return
            }
            if exist {
                fmt.Println("business process exist!")
                time.Sleep(time.Second * 5)
                continue
            }
            fmt.Println("start business process...")
            command := exec.Command(fmt.Sprintf(fmt.Sprintf("%s/go_start", basePath), "-business", "-c", os.Args[2], "-d", os.Args[4], "-e", os.Args[6]))
            command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
            if comerr := command.Start(); comerr != nil {
                fmt.Println("start business process failed, " + comerr.Error())
                return
            }
            time.Sleep(time.Second * 5)
            businessPid := command.Process.Pid
            exist, checkerr = utils.CheckProRunning("go_start | grep business")
            if checkerr != nil {
                fmt.Println("check business process failed, " + checkerr.Error())
                return
            }
            if exist {
                fmt.Println(fmt.Sprintf("start business process suceess, pid is %d", businessPid))
            } else {
                fmt.Println("warning! start business process fail...")
            }
        }
    }
    // ------------------------ 守护进程 end ------------------------
    // ------------------------ 业务进程 start ------------------------
    fmt.Println("hello, welcome to business detail!")
}

相关工具方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package utils
import (
    "os"
    "fmt"
    "go_start/core/global"
    "os/exec"
    "runtime"
    "strconv"
    "strings"
    "syscall"
)
func StopBusinessProcess(serverName string) {
    global.G_LOG.Info("start to stop business...")
    pid, _ := GetPid(serverName)
    if pid > 0 {
        global.G_LOG.Info(fmt.Sprintf("stop %s ...", serverName))
        syscall.Kill(pid, syscall.SIGKILL)
        global.G_LOG.Info(fmt.Sprintf("stop business success, pid is %d", pid))
    }
}
//根据进程名判断进程是否运行
func CheckProRunning(serverName string) (bool, error) {
    a := `ps -ef|grep ` + serverName + `|grep -v grep|awk '{print $2}'`
    pid, err := runCommand(a)
    if err != nil {
        return false, err
    }
    return pid != "", nil
}
//根据进程名称获取进程ID
func GetPid(serverName string) (pid int, err error) {
    a := `ps -ef|grep ` + serverName + `|grep -v grep|awk '{print $2}'`
    var pidStr string
    if pidStr, err = runCommand(a); err != nil {
        return
    }
    pid, err = strconv.Atoi(pidStr)
    return
}
func runCommand(cmd string) (string, error) {
    if runtime.GOOS == "windows" {
        return runInWindows(cmd)
    } else {
        return runInLinux(cmd)
    }
}
func runInWindows(cmd string) (string, error) {
    result, err := exec.Command("cmd", "/c", cmd).Output()
    if err != nil {
        return "", err
    }
    return strings.TrimSpace(string(result)), err
}
func runInLinux(cmd string) (string, error) {
    result, err := exec.Command("/bin/sh", "-c", cmd).Output()
    if err != nil {
        return "", err
    }
    return strings.TrimSpace(string(result)), err
}

说明

1、启动go_start二进制文件,方式:./go_start -c param1 -d param2 -e param3,这里第一次进入main方法

2、main方法中,os.Args = [./go_start -c param1 -d param2 -c param3],此时不包含"-daemon"参数,进入step2,走创建守护进程代码分支,执行创建守护进程,exec.Command(./go_start -c param1 -d param2 -e param3 -daemon),第二次进入main方法

3、main方法中,os.Args = [./go_start -c param1 -d param2 -c param3 -daemon],此时包含"-daemon",进入step3,走创建业务进程分支,执行创建业务进程,exec.Command(./go_start -c param1 -d param2 -e param3);此时守护进程存在,每隔5秒监听一次业务进程是否存在,如果存在则不操作;不存在则重新执行创建业务进程exec.Command(./go_start -c param1 -d param2 -e param3);

4、执行具体的业务进程逻辑

验证

ps -ef | grep go_start

]$ 110  1   ./go_start -c param1 -d param2 -c param3             -- ①
]$ 111  1   ./go_start -c param1 -d param2 -c param3 -daemon     -- ②
]$ 112  111 ./go_start -business -c param1 -d param2 -c param3   -- ③

刚开始会出现三个进程,假设进程id如上,一会之后①会消失,这是正常的,因为刚开始的启动就是①,然后只剩下进程②和③

]$ 111  1   ./go_start -c param1 -d param2 -c param3 -daemon     -- ②
]$ 112  111 ./go_start -business -c param1 -d param2 -c param3   -- ③

验证kill业务进程:会启动新的业务进程,守护进程不变;所以执行:kill 112

]$ 111  1   ./go_start -c param1 -d param2 -c param3 -daemon     -- ②
]$ 112  111 [go_start] <defunct>                                 -- ③'
]$ 113  111 ./go_start -business -c param1 -d param2 -c param3   -- ③

这里kill 112后,会出现一个僵尸进程,不影响实际业务进程的创建和运行,不需要理会;假设新创建的业务进程pid为113

验证kill守护进程:整个程序退出,也就是执行ps -ef | grep go_start后,没有对应的守护进程和业务进程,同时僵尸进程也会消失;得益于以下代码,进程组

command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

综上

纯golang语言形式实现了守护进程,针对启动业务进程,优化点:可以使用go func(){}()协程方式启动更优雅,这里先不实施,待后续有空改进;

缺点:依然要通过参数控制守护进程和业务进程,-daemon -business,期望统一起来,不用参数控制

放在(3)实现

附录

以下是关于信号量的一个记录,当作参考文档

信号 动作 说明
SIGHUP 1 Term 终端控制进程结束(终端连接断开)
SIGINT 2 Term 用户发送INTR字符(Ctrl+C)触发
SIGQUIT 3 Core 用户发送QUIT字符(Ctrl+/)触发
SIGILL 4 Core 非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT 6 Core 调用abort函数触发
SIGFPE 8 Core 算术运行错误(浮点运算错误、除数为零等)
SIGKILL 9 Term 无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV 11 Core 无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE 13 Term 消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM 14 Term 时钟定时信号
SIGTERM 15 Term 结束程序(可以被捕获、阻塞或忽略)
SIGUSR1 30,10,16 Term 用户保留
SIGUSR2 31,12,17 Term 用户保留
SIGCHLD 20,17,18 Ign 子进程结束(由父进程接收)
SIGCONT 19,18,25 Cont 继续执行已经停止的进程(不能被阻塞)
SIGSTOP 17,19,23 Stop 停止进程(不能被捕获、阻塞或忽略) SIGTSTP 18,20,24 Stop 停止进程(可以被捕获、阻塞或忽略) SIGTTIN 21,21,26 Stop 后台程序从终端中读取数据时触发 SIGTTOU 22,22,27 Stop 后台程序向终端中写数据时触发

到此这篇关于Golang守护进程用法示例分析的文章就介绍到这了,更多相关Golang守护进程内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/dkjhl/article/details/129691625

延伸 · 阅读

精彩推荐
  • GolangGo语言调用其它程序并获得程序输出的方法

    Go语言调用其它程序并获得程序输出的方法

    这篇文章主要介绍了Go语言调用其它程序并获得程序输出的方法,实例分析了Go调用cmd程序的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    Go语言编程实例3202020-04-18
  • GolangGo语言生成素数的方法

    Go语言生成素数的方法

    这篇文章主要介绍了Go语言生成素数的方法,实例分析了Go语言生成素数的技巧,需要的朋友可以参考下 ...

    依山带水3212020-04-21
  • GolangGO语言 复合类型专题

    GO语言 复合类型专题

    这篇文章主要介绍了GO语言 复合类型的的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下...

    柠檬橙10242742020-07-21
  • GolangGO语言Defer用法实例分析

    GO语言Defer用法实例分析

    这篇文章主要介绍了GO语言Defer用法,实例分析了Defer的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    niuniu5092020-04-20
  • Golang一文搞懂Golang中iota的用法和原理

    一文搞懂Golang中iota的用法和原理

    我们知道iota是go语言的常量计数器,本文尝试全面总结其使用用法以及其实现原理,需要的朋友可以参考以下内容,希望对大家有所帮助...

    yi个俗人5452022-08-30
  • GolangGo 语言选择器实例教程

    Go 语言选择器实例教程

    这篇文章主要为大家介绍了Go 语言选择器实例教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    cureking8112022-07-14
  • GolangGo+Vue开发一个线上外卖应用的流程(用户名密码和图形验证码)

    Go+Vue开发一个线上外卖应用的流程(用户名密码和图形验证码)

    这篇文章主要介绍了Go+Vue开发一个线上外卖应用(用户名密码和图形验证码),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需...

    you-men11202021-02-01
  • GolangvictoriaMetrics库布隆过滤器初始化及使用详解

    victoriaMetrics库布隆过滤器初始化及使用详解

    这篇文章主要为大家介绍了victoriaMetrics库布隆过滤器初始化及使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪...

    charlieroro7922022-09-13