介绍go中的生成进程, 执行进程, 信号和退出
Spawning Processes 有时,我们的Go程序需要派生其他非Go进程。
spawning-processes.go 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 package mainimport ( "fmt" "io" "os/exec" ) func main () { dateCmd := exec.Command("date" ) dateOut, err := dateCmd.Output() if err != nil { panic (err) } fmt.Println("> date" ) fmt.Println(string (dateOut)) _, err = exec.Command("date" , "-x" ).Output() if err != nil { switch e := err.(type ) { case *exec.Error: fmt.Println("failed executing:" , err) case *exec.ExitError: fmt.Println("command exit rc =" , e.ExitCode()) default : panic (err) } } grepCmd := exec.Command("grep" , "hello" ) grepIn, _ := grepCmd.StdinPipe() grepOut, _ := grepCmd.StdoutPipe() grepCmd.Start() grepIn.Write([]byte ("hello grep\ngoodbye grep" )) grepIn.Close() grepBytes, _ := io.ReadAll(grepOut) grepCmd.Wait() fmt.Println("> grep hello" ) fmt.Println(string (grepBytes)) lsCmd := exec.Command("bash" , "-c" , "ls -a -l -h" ) lsOut, err := lsCmd.Output() if err != nil { panic (err) } fmt.Println("> ls -a -l -h" ) fmt.Println(string (lsOut)) }
生成的程序返回的输出结果与我们直接从命令行运行它们的结果相同。date 没有 -x 标志,因此它会以错误信息和非零返回代码退出。
log 1 2 3 4 5 6 7 8 9 10 11 12 $ go run spawning-processes.go > date Thu 05 May 2022 10:10:12 PM PDT command exited with rc = 1 > grep hello hello grep > ls -a -l -hdrwxr-xr-x 4 mark 136B Oct 3 16:29 . drwxr-xr-x 91 mark 3.0K Oct 3 12:50 .. -rw-r--r-- 1 mark 1.3K Oct 3 16:28 spawning-processes.go
Exec'ing Processes 在上一个示例中,我们了解了如何生成外部进程。当我们需要一个外部进程可以访问正在运行的 Go 进程时,我们就会这样做。有时,我们只是想用另一个进程(可能是非 Go 进程)完全替换当前的 Go 进程。为此,我们将使用 Go 实现的经典 exec 函数。
execing-processes.go 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 package mainimport ( "os" "os/exec" "syscall" ) func main () { binary, lookErr := exec.LookPath("ls" ) if lookErr != nil { panic (lookErr) } args := []string {"ls" , "-a" , "-l" , "-h" } env := os.Environ() execErr := syscall.Exec(binary, args, env) if execErr != nil { panic (execErr) } }
当我们运行程序时,它将被 ls 取代。
log 1 2 3 4 5 $ go run execing-processes.go total 16 drwxr-xr-x 4 mark 136B Oct 3 16:29 . drwxr-xr-x 91 mark 3.0K Oct 3 12:50 .. -rw-r--r-- 1 mark 1.3K Oct 3 16:28 execing-processes.go
请注意,Go 并不提供经典的 Unix fork 函数。不过这通常不是问题,因为启动 goroutines、生成进程和执行进程涵盖了 fork 的大多数使用情况。
Signals 有时,我们希望 Go 程序能智能地处理 Unix 信号。例如,我们可能希望服务器在收到 SIGTERM 时优雅地关闭,或者命令行工具在收到 SIGINT 时停止处理输入。下面介绍如何在 Go 中使用通道处理信号。
signals.go 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 package mainimport ( "fmt" "os" "os/signal" "syscall" ) func main () { sigs := make (chan os.Signal, 1 ) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) done := make (chan bool , 1 ) go func () { sig := <-sigs fmt.Println() fmt.Println(sig) done <- true }() fmt.Println("awaiting signal" ) <-done fmt.Println("exiting" ) }
log 1 2 3 4 5 $ go run signals.go awaiting signal ^C interrupt exiting
当我们运行这个程序时,它会阻塞等待信号。通过键入 ctrl-C(终端显示为 ^C),我们可以发送 SIGINT 信号,使程序打印中断,然后退出。
Exit 使用 os.Exit 可以立即以给定的状态退出。
exit.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" "os" ) func main () { defer fmt.Println("!" ) os.Exit(3 ) }
请注意,与 C 不同,Go 不使用 main 的整数返回值来指示退出状态。如果要以非零状态退出,则应使用 os.Exit。 如果使用 go run 运行 exit.go,退出将被 go 提取并打印出来。 通过构建和执行二进制文件,你可以在终端中看到状态。
log 1 2 3 4 5 6 7 $ go run exit.go exit status 3 $ go build exit.go $ ./exit $ echo $?3
请注意,我们程序中的 ! 从未打印出来。
参考链接