golang 后台进程的启动和停止操作

启动命令

我们先来个非后台运行的启动命令

func init() {
    startCmd := &cobra.Command{
        Use:   "start",
        Short: "Start Gonne",
        Run: func(cmd *cobra.Command, args []string) {
            startHttp()
        },
    }
    startCmd.Flags().BoolVarP(&daemon, "deamon", "d", false, "is daemon?")
    RootCmd.AddCommand(startCmd)

}

startHttp方法启动一个http的web服务

func startHttp() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello cmd!")
    })
    if err := http.ListenAndServe(":9090", nil); err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

现在通过gonne start便可以启动一个web服务了,但是程序停留在命令行,如果ctrl+C程序也会终止了

命令行参数

如果想要后台启动,那么得让start命令知道是要后台运行的,参照docker命令行的方式就是加上-d,给一个命令添加参数的判断只需很少的代码

改造一下代码

func init() {
    var daemon bool
    startCmd := &cobra.Command{
        Use:   "start",
        Short: "Start Gonne",
        Run: func(cmd *cobra.Command, args []string) {
            if daemon {
        fmt.Println("gonne start",daemon)
            }
            startHttp()
        },
    }
    startCmd.Flags().BoolVarP(&daemon, "deamon", "d", false, "is daemon?")
    RootCmd.AddCommand(startCmd)

}

命令行输入

gonne start -d

这样就可以接收到-d参数了,这里要说明一下,第一个参数取值,第二个参数代码--deamon,第三个参数代表-d

第四个参数代码不加-d时候的默认值,第五参数是描述

后台运行

后台运行其实这里使用的是一个巧妙的方法,就是使用系统的command命令行启动自己的命令行输入,是不是有点绕,再看看看改造后的代码

Run: func(cmd *cobra.Command, args []string) {
  if daemon {
    command := exec.Command("gonne", "start")
    command.Start()
    fmt.Printf("gonne start, [PID] %d running...\n", command.Process.Pid)
    ioutil.WriteFile("gonne.lock", []byte(fmt.Sprintf("%d", command.Process.Pid)), 0666)
    daemon = false
    os.Exit(0)
  } else {
    fmt.Println("gonne start")
  }
  startHttp()
},

用exec的Command启动刚输入的gonne start -d,就会拦截到这条请求然后通过gonne start,但是程序就不会停留在命令行了,然后发现http服务还在,还可以访问。

还有一点就是把pid输出到gonne.lock文件,给停止的程序调用

终止后台程序

有了之前的操作后,停止就简单多了

func init() {
    RootCmd.AddCommand(stopCmd)
}

var stopCmd = &cobra.Command{
    Use:   "stop",
    Short: "Stop Gonne",
    Run: func(cmd *cobra.Command, args []string) {
        strb, _ := ioutil.ReadFile("gonne.lock")
        command := exec.Command("kill", string(strb))
        command.Start()
        println("gonne stop")
    },
}

执行 gonne stop 即可终止之前启动的http服务

help命令

好了,关于命令的操作讲完了,再看看cobra给的福利,自动生成的help命令

这个不需要你做什么操作,只需要输入gonne help,相关信息已经帮你生产好了。

appletekiMacBook-Pro:andev apple$ gonne help
Usage:
  gonne [flags]
  gonne [command]

Available Commands:
  help        Help about any command
  start       Start Gonne
  stop        Stop Gonne
  version     Print the version number of Gonne

Flags:
  -h, --help   help for gonne

Use "gonne [command] --help" for more information about a command.

当然,子命令也有

appletekiMacBook-Pro:andev apple$ gonne start -h
Start Gonne

Usage:
  gonne start [flags]

Flags:
  -d, --deamon   is daemon?
  -h, --help     help for start

自此告别各种脚本!

补充:golang子进程的启动和停止,mac与linux的区别

今天接到一个任务是将原来运行在mac的应用移植到linux,原因当然是因为客户那边当前是linux环境,也不想再采购mac电脑。

通常来说,这个工作并不难,因为我选用的服务器端技术是c或者golang,这两种技术具有很好的可移植性,而且大多是重新编译即可运行,所以接到任务的开始并没有把这个当一回事。

跟想象中的也差不多,搭建好linux测试服务器,在mac上把运行很久的应用重新交叉编译了一遍,部署到linux实验环境,启动、测试,看起来一切正常。准备打包交活,这时候发现一个问题,程序无法终止。

简单调试后就找到了原因,在系统中启动的子进程,发出终止信号之后居然仍在运行,导致父进程也一直无法退出,尴尬了。

列一下采用的代码(代码为简化版仅供示例):

func startChild1() {
 cmd := exec.Command("/bin/sh", "-c", "sleep 1000")
 time.AfterFunc(10*time.Second, func() {
  fmt.Println("PID1=", cmd.Process.Pid)
  syscall.Kill(-cmd.Process.Pid, syscall.SIGQUIT)
  fmt.Println("killed")
 })
 fmt.Println("begin run")
 cmd.Run()
}

示例代码首先启动一个sleep的子进程,表示某个子业务开始工作,然后延时10秒钟之后,把这个子进程杀死。这段代码启动子进程和关闭子进程在mac电脑的原有系统上工作都很正常,但是到了linux,启动子进程仍然没有问题,关闭子进程不成功。

检查了一下在linux的工作过程,发现启动子进程之后,实际上是启动了两个进程,一个进程是/bin/sh,随后sh又启动了一个子进程自身的子进程sleep。而发出退出命令的时候,只有sh退出了,sleep进程仍然继续运行。对比同样的mac电脑上,sh进程是没有出现的,只有一个sleep进程,所以发出退出命令的时候,sleep正常关闭,系统表现正常。

使用/bin/sh来启动另外的命令行程序是有原因的,这源于golang本身的设计,golang的exec.Command,后面第一个参数是命令行程序本身,之后的每一个exec.Command参数,都代表命令行程序的一个参数,而不是我们常用的,命令行程序路径和参数都可以写在一个字符串,用空格隔开即可。所以有的时候我们是为了省事,也有的时候是顺手移植了别的语言的代码,就使用/bin/sh来启动需要的命令行程序,就如同上面示例代码一样,这样情况下,除了-c参数要单独占用一个字符串,我们原本要启动的字符串程序及其参数,就可以如同常见语言处理方式那样,放在一个字符串了。

我们可以尝试一下这个代码:

func startChild2() {
 cmd := exec.Command("sleep", "1000")
 time.AfterFunc(10*time.Second, func() {
  fmt.Println("PID2=", cmd.Process.Pid)
  syscall.Kill(-cmd.Process.Pid, syscall.SIGQUIT)
  fmt.Println("killed")
 })
 fmt.Println("begin run")
 cmd.Run()
}

测试一下,这段代码因为没有经过/bin/sh程序,在linux上也只有sleep这一个进程被建立,直接向其发出退出指令是可以正常工作的。这从进程的观察中及实验的结果中,都可以证实我们的判断。

知道了原因,处理起来也很容易,一是把程序改成类似上面这样的方式启动进程。另外一个办法则是直接为/bin/sh及我们的命令行进程建立一个进程组,这样最后发出的指令退出这个进程组,同样可以同时退出/bin/sh及sleep两个进程,达到我们的需求。

写代码测试一下:

func startChild3() {
 cmd := exec.Command("/bin/sh", "-c", "sleep 1000")
 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
 time.AfterFunc(10*time.Second, func() {
  fmt.Println("PID3=", cmd.Process.Pid)
  syscall.Kill(-cmd.Process.Pid, syscall.SIGQUIT)
  fmt.Println("killed")
 })
 fmt.Println("begin run")
 cmd.Run()
}

经过实际测试,这段代码在不改变原有的命令行参数传递习惯的基础上,可以正常在linux及mac电脑顺利执行。

最后再说一下命令cmd.Process.Signal,golang文档上说的很清楚,这是向进程发送消息信号,比如同样的syscall.SIGQUIT,这也是告诉子进程退出的意思。所以大多的应用中,我们希望一个进程退出,直接用:

cmd.Process.Signal(syscall.SIGQUIT)

也是可以正常执行的,但对于我们上面说的情况,如果先使用/bin/sh启动了另外一个子进程,这种方法就无效了(指在linux无效,mac测试是一样可以用的,关键区别同样是在mac,/bin/sh进程不会保留并等待我们启动的子进程退出,所以退出消息可以正常的发送到正常的子进程)。

所以为了跨平台的通用性,建议还是使用Process.Kill或者syscall.Kill来杀死子进程。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Golang信号处理及如何实现进程的优雅退出详解

    Linux系统中的信号类型 各操作系统的信号定义或许有些不同.下面列出了POSIX中定义的信号. 在linux中使用34-64信号用作实时系统中. 命令 man 7 signal 提供了官方的信号介绍.也可以是用kill -l来快速查看 列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号).不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会. Linux支持的标准信号有以下一

  • golang 输出重定向:fmt Log,子进程Log,第三方库logrus的详解

    独立 fmt Log输出重定向 golang的fmt包的输出函数 Println.Printf.PrintStack等,默认将打印输出到os.Stdout.错误打印输出到os.Stderr,os.Stdout 和 os.Stderr 默认值 /dev/stdout /dev/stderr 设备. //代码摘自:golang封装包 -> /lib/golang/src/os var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin&q

  • golang守护进程用法示例

    本文实例讲述了golang守护进程用法.分享给大家供大家参考,具体如下: 用node写了一个socket后台服务,可是有时候会挂,node一个异常就game over了,所以写了一个守候. 复制代码 代码如下: package main import (         "log"         "os"         "os/exec"         "time" ) func main() {         lf,

  • golang如何实现mapreduce单进程版本详解

    前言   MapReduce作为hadoop的编程框架,是工程师最常接触的部分,也是除去了网络环境和集群配 置之外对整个Job执行效率影响很大的部分,所以很有必要深入了解整个过程.元旦放假的第一天,在家没事干,用golang实现了一下mapreduce的单进程版本,github地址.处理对大文件统计最高频的10个单词,因为功能比较简单,所以设计没有解耦合.   本文先对mapreduce大体概念进行介绍,然后结合代码介绍一下,如果接下来几天有空,我会实现一下分布式高可用的mapreduce版本.

  • golang 后台进程的启动和停止操作

    启动命令 我们先来个非后台运行的启动命令 func init() { startCmd := &cobra.Command{ Use: "start", Short: "Start Gonne", Run: func(cmd *cobra.Command, args []string) { startHttp() }, } startCmd.Flags().BoolVarP(&daemon, "deamon", "d&q

  • C#对Windows服务组的启动与停止操作

    Windows服务大家都不陌生,Windows服务组的概念,貌似MS并没有这个说法. 作为一名软件开发者,我们的机器上安装有各种开发工具,伴随着各种相关服务. Visual Studio可以不打开,SqlServer Management Studio可以不打开,但是SqlServer服务却默认开启了.下班后,我的计算机想用于生活.娱乐,不需要数据库服务这些东西,尤其是在安装了Oracle数据库后,我感觉机器吃力的很. 每次开机后去依次关闭服务,或者设置手动开启模式,每次工作使用时依次去开启服务

  • postgresql 启动与停止操作

    启动和停止数据库服务器 service 方式 service postgresql-10 start service postgresql-10 stop service postgresql-10 status pg_ctl 方式 pg_ctl start -D [ data 所在路径 ] pg_ctl stop -D [ data 所在路径 ] 三种形式:-m 指定模式 smart 模式:会等待活动的事务提交结束,并等待客户端主动断开连接之后关闭数据库服务 fast 模式:会回滚所有的活动的

  • Mysql服务器的启动与停止(一)

    在讨论如何启动MySQL服务器之前,让我们考虑一下应该以什么用户身份运行MySQL服务器.服务器可以手动或自动启动.如果你手动启动它,服务器以你登录Unix(Linux)的用户身份启动,即如果你用paul登录Unix并启动服务器,它用paul运行:如果你用su命令切换到root,然后运启动服务器,则它以root运行.然而,大多数情况下你可能不想手动启动服务器,最有可能是你安排MySQL服务器在系统引导时自动启动,作为标准引导过程的一部分,在Unix下,该引导过程由系统的Unix用户root执行,

  • Nginx 启动、停止、重启、升级操作命令收集

    那下面主要总结一下Nginx的基本操作. 启动操作 命令: nginx -c /usr/nginx/conf/nginx.conf -c参数指定了要加载的nginx配置文件路径. 停止操作 停止操作是通过向nginx进程发送信号(什么是信号请参阅linux文章)来进行的 步骤1:查询nginx主进程号 ps -ef | grep nginx 在进程列表里面找master进程,它的编号就是主进程号了. 步骤2:发送信号 从容停止Nginx: kill -QUIT 主进程号 快速停止Nginx: k

  • mongodb数据库入门学习笔记之下载、安装、启动、连接操作解析

    本文实例讲述了mongodb数据库下载.安装.启动.连接操作.分享给大家供大家参考,具体如下: 简介: MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. 1.下载 从官网下载压缩包, 官网地址:https://www.mongodb.com/download-center/v2/community. 下载命

  • Linux编辑启动、停止与重启springboot jar包脚本实例

    前言 springboot的配置文件中,配置文件的名字都有各自的意义跟用途 dev 开发环境 prod 生产环境(默认) test 测试环境 加载指定配置文件 --spring.profiles.active=prod springboot加载jar包的方式有 // 直接在控制台进行启动,缺点就是控制台关闭项目也就关闭了. java -jar bootdo.jar // 这种方式可以运行在后台,但是如果推出了shell的话,那也会挂 java -jar /bootdo-2.0.0.jar > b

  • Spring Boot 启动、停止、重启、状态脚本

    此脚本用来管理 SpringBoot 项目的进程状态. 有提示功能. 把脚本丢到项目文件夹, 添加执行权限即可. 如果 jenkins 使用这个脚本, 需要在 java -jar 命令前添加 BUILD_ID=dontKillMe , 不然 jenkins 会杀掉进程. 参考: https://stackoverflow.com/questions/39169457/how-to-tell-jenkins-not-to-kill-processes-after-successful-execut

  • 使用批处理实现启动和停止服务的代码分析(net start&net stop)

    使用windows自带的net.exe功能启动或停止服务 启动/停止 服务的命令行格式为: net start / stop服务名称 (注意:服务先要设置为手动启动类型) 例子: 以启动和停止Oracle11g服务为例(ORCL是数据库名 ): 复制代码 代码如下: %启动Oracle的服务% @echo.服务启动...... @echo off net start OracleVssWriterORCL net start OracleDBConsoleorcl net start Oracl

  • golang针对map的判断,删除操作示例

    本文实例讲述了golang针对map的判断,删除操作.分享给大家供大家参考,具体如下: map是一种key-value的关系,一般都会使用make来初始化内存,有助于减少后续新增操作的内存分配次数.假如一开始定义了话,但没有用make来初始化,会报错的. 复制代码 代码如下: package main import ( "fmt" ) func main(){ var test =  map[string]string{"姓名":"李四",&qu

随机推荐