接受的答案对于信号应该在哪里处理是模糊的。我认为如果可能的话,必须使用一些更复杂的技术来防止向孩子发送中断。
TLDR;
因此,处理ctrl-c 的唯一方法是预测 SIGINT 并在子级中处理该信号。
我自己做了一些实验。
go build -o ctrl-c ctrl-c.go
如果程序被发送到后台,杀死主进程的唯一方法是使用kill -9 (SIGKILL)。
SIGTERM (15) 不行。
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill $cmd
[1] 1165918
1165918
bashed 1165926
bashed 1165927
bashed 1165928
main()
go SIGN 23 urgent I/O condition
go SIGN 23 urgent I/O condition
main()
kill 1165918
go SIGN 15 terminated
main()
$ main()
main()
main()
main()
main()
main() done.
Bash _ 1165926 EXITs
Bash q 1165927 EXITs
Bash c 1165928 EXITs
[1]+ Done ./ctrl-c
SIGINT (2) 不行。
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill -INT $cmd
[1] 1167675
1167675
bashed 1167683
bashed 1167684
bashed 1167685
main()
main()
kill 1167675
go SIGN 2 interrupt
main()
balmora: ~/src/my/go/doodles/sub-process [master]
$ main()
main()
main()
main()
main()
main() done.
Bash _ 1167683 EXITs
Bash q 1167684 EXITs
Bash c 1167685 EXITs
SIGKILL 会杀死主进程,但不会杀死 bash 子命令。
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill -KILL $cmd
[1] 1170721
1170721
bashed 1170729
bashed 1170730
bashed 1170731
main()
main()
kill 1170721
[1]+ Killed ./ctrl-c
Bash _ 1170729 EXITs
Bash q 1170730 EXITs
Bash c 1170731 EXITs
但是,如果 go 二进制文件在前台运行,那么只有处理 SIGINT 的子进程才会继续运行。这感觉与上述发现几乎相反
$ ./ctrl-c
1186531
bashed 1186538
bashed 1186539
bashed 1186540
main()
main()
main()
main()
main()
main()
^C
Bash c 1186540 INTs quit
Bash q 1186539 INTs ignored
Bash c 1186540 EXITs
Bash _ 1186538 INTs ignored
go SIGN 2 interrupt
go SIGN 17 child exited
6q ELAPSED 2
Bash q 1186539 EXITs
6_ ELAPSED 2
Bash _ 1186538 EXITs
go SIGN 17 child exited
main()
main()
main() done.
无论如何,对我来说,当使用Cmd.Start() 时,ctrl+c 被转发给孩子们。如果使用Cmd.Run(),则行为相同,但Cmd.Run() 将在每个子命令退出之前等待。在 go 例程 (go func(){}()) 中运行 Cmd 不会改变任何内容。如果子命令作为 go-routine 或使用 Cmd.Start()“并行”启动,则中断信号将同时到达所有子命令。
为了在中断后保持子命令在交互式终端上运行,我认为子命令必须处理信号并忽略它。
我试验的代码:
package main
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
func signs(s ...os.Signal) chan os.Signal {
signals := make(chan os.Signal, 1)
signal.Notify(signals, s...)
signal.Notify(signals,
os.Interrupt, syscall.SIGINT, syscall.SIGQUIT, // keyboard
syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM, // os termination
syscall.SIGUSR1, syscall.SIGUSR2, // user
syscall.SIGPIPE, syscall.SIGCHLD, syscall.SIGSEGV, // os other
)
return signals
}
func interpret(signals chan os.Signal) chan os.Signal {
go func() {
for ;; {
select {
case sign := <-signals:
elog("go SIGN %#v %s", sign, sign)
}
}
}()
return signals
}
func bash(script string) {
cmd := exec.Command("/bin/bash", "-c", script )
cmd.Stdout = os.Stderr
err := cmd.Start()
//err := cmd.Run()
if err != nil {
log.Fatal(err)
}
elog("bashed %d", cmd.Process.Pid)
}
func main() {
fmt.Println(os.Getpid())
signals := interpret(signs())
signals = signals
//go bash(`
bash(`
trap ' echo Bash _ $$ INTs ignored; ' SIGINT
trap ' echo Bash _ $$ QUITs ignored; ' SIGQUIT
trap ' echo Bash _ $$ EXITs' EXIT
sleep 6;
echo 6_ $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n _; sleep 0.1; done; echo
`)
// go bash(`
bash(`
trap ' echo Bash q $$ INTs ignored; ' SIGINT
trap ' echo Bash q $$ QUITs; exit ' SIGQUIT
trap ' echo Bash q $$ EXITs; ' EXIT
sleep 6;
echo 6q $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n q; sleep 0.1; done; echo
`)
//go bash(`
bash(`
trap ' echo Bash c $$ INTs quit; exit ' SIGINT
trap ' echo Bash c $$ QUITs ignored; ' SIGQUIT
trap ' echo Bash c $$ EXITs' EXIT
sleep 6;
echo 6c $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n c; sleep 0.1; done; echo
`)
go func() {
for ;; {
time.Sleep(time.Millisecond * 333)
elog("main()")
}
}()
time.Sleep(3 * time.Second)
elog("main() done.")
}
func echo(a ...interface{}) {
_, err := fmt.Println(a...)
if err != nil {
fmt.Println("ERR ", err.Error())
}
}
func elog(form string, arg ...interface{}) {
println(fmt.Sprintf(form, arg...))
}