今天接到一个任务是将原来运行在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来杀死子进程。

本文参考及引用了以下链接,在此致谢:

https://studygolang.com/articles/10083

golang子进程的启动和停止,mac与linux的区别的更多相关文章

  1. 3)nginx的启动与停止、重启,linux配置对外端口

    [启动] 启动代码格式:nginx安装目录地址 -c nginx配置文件地址例如: [root@LinuxServer sbin]# /usr/local/nginx/sbin/nginx -c /u ...

  2. Nginx的启动、停止与重启---linux

    一.选定安装文件目录 可以选择任何目录 cd /usr/local/src 二.安装PCRE库 ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcr ...

  3. [Shell]sed命令在MAC和Linux下的不同使用方式

    ---------------------------------------------------------------------------------------------------- ...

  4. mac上启动和停止mysql

    因调试需要,在mac上安装了mysql,安装方法网上大把,此处不赘述.启动和停止命令每次要手工敲,因此写个小脚本方便自己: startmysql.sh(/Applications/Develop/my ...

  5. [DabaBase] MongoDB (6) 启动、停止、相关系统配置及安全性设置

    MongoDB 启动.停止.相关系统配置及安全性设置 启动MongoDB ./mongod —-help 查看帮助说明 mac-abeen:bin abeen$ sudo ./mongod --dbp ...

  6. pg_ctl -- 启动、停止、重启 PostgreSQL

    pg_ctl 名称 pg_ctl -- 启动.停止.重启 PostgreSQL 语法 pg_ctl start [-w] [-s] [-D datadir] [-l filename] [-o opt ...

  7. Windows 64下elasticsearch-1.7.1集群 安装、启动、停止

    elasticsearch-1.7.1 (es Windows 64) 安装.启动.停止的详细记录 https://blog.csdn.net/qq_27093465/article/details/ ...

  8. Nginx 启动、停止、平滑重启和平滑升级 graceful shutdown of worker processes

    Controlling nginx http://nginx.org/en/docs/control.html nginx can be controlled with signals. The pr ...

  9. pg_ctl — 启动、停止、重启 PostgreSQL

    pg_ctl 名称 pg_ctl -- 启动.停止.重启 PostgreSQL 语法 pg_ctl start [-w] [-s] [-D datadir] [-l filename] [-o opt ...

随机推荐

  1. python中for嵌套打印图形

    # 打印出九九乘法表 1 * 1 = 1 2 * 1 = 2 2 * 2 = 4 3 * 1 = 3 3 * 2 = 6 3 * 3 = 9 4 * 1 = 4 4 * 2 = 8 4 * 3 = 1 ...

  2. URL控制器

    自定义路由 from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^books/$', view ...

  3. <玩转Django2.0>读书笔记:URL规则和视图

    1. 带变量的URL #urls.py from django.urls import path from .view import * urlpatterns = [ path('',index_v ...

  4. MQTT之Mosquitto

    https://mosquitto.org/ Eclipse Mosquitto是一个开源(EPL / EDL许可)消息代理,它实现了MQTT协议版本3.1和3.1.1.Mosquitto重量轻,适用 ...

  5. python学习:continue及break使用

    continue及break使用 #continue 作用:结束本次循环,继续下次循环#break 作用:跳出整个当次循环 for i in range(10): if i < 5: conti ...

  6. 获取subgrid中的数据并修改,含添加刷新列表的事件

    var isAddRefresh = false; function setLawsuitQueryResultText() { var queryResultIndex = 7; var gridC ...

  7. Java for Android 第二周课上实验一

    (一)命令行下程序开发 (二)IDEA下程序开发调试 Mac OS系统下使用的IDEA为 Netbeans (三)测试题我的学号后两位为10 使用简单的PHP小程序得我的题目为2:实现简单四则运算(能 ...

  8. docker 安装 zookeeper

    镜像下载hub.docker.com 上有不少 ZK 镜像, 不过为了稳定起见, 我们就使用官方的 ZK 镜像吧.首先执行如下命令: docker pull zookeeper当出现如下结果时, 表示 ...

  9. 认识Python和基础知识

    一.第一个Python程序 用vim编写,格式: vim hello.py 然后按 i 进入插入模式开始写代码,注意所有的代码都要用英文输入法,先写括号和引号,再在引号里面写内容(为了避免引号.括号不 ...

  10. Lock、ReentrantLock、ReentrantReadWriteLock区别

    Lock Lock相比于synchronized具有更强大的功能,在jdk1.6之前,锁竞争激烈的情况下使用lock的实现类ReentrantLock甚至比synchronized具有更好的性能,1. ...