加载含信号处理器的Go共享库后,Python input/getpass无法响应Ctrl+C,仅改Go代码可修复吗?
仅修改Go代码修复Python调用Go共享库后Ctrl+C无法中断input()/getpass()的问题
问题背景
加载带有信号处理逻辑的Go共享库后,Python里的input()或getpass()没法响应Ctrl+C:调用Go函数时按Ctrl+C能正常中断Go的逻辑,但之后用Python输入方法时,Ctrl+C完全没反应,得按Ctrl+D或者先按Ctrl+C再敲Enter才能退出。现在不想改Python代码(比如提前保存恢复信号处理器),只改Go代码能不能搞定?
复现步骤
- 把下面的
main.go和main.py存到同一个目录 - Linux/Mac下执行:
go build -o main.so -buildmode=c-shared main.go生成共享库 - 跑
python3 main.py,按Ctrl+C中断Go的长任务 - 轮到
input()/getpass()时,按Ctrl+C,发现无法正常退出
问题根源
Go的signal.Notify()会直接替换进程全局的信号处理函数。原Go代码里用signal.Reset()是把信号处理恢复到系统默认行为(比如SIGINT默认直接终止进程),但Python原本对SIGINT的处理是自定义的——会抛出KeyboardInterrupt异常让程序优雅退出。Go函数退出后,信号处理被改成了系统默认,Python的输入逻辑自然没法识别Ctrl+C了。
修复方案(只改Go代码)
核心就是:在Go接管信号之前,先把Python设置的原信号处理器存下来,等Go函数干完活退出时,再把原处理器恢复回去,而不是重置成系统默认。
修改后的main.go代码:
package main import ( "C" "context" "fmt" "os" "os/signal" "syscall" "time" ) //export addNums func addNums(a int, b int) int { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() // 先保存Python原来的SIGINT和SIGTERM信号处理函数 var oldSigInt syscall.Sigaction var oldSigTerm syscall.Sigaction if err := syscall.Sigaction(syscall.SIGINT, nil, &oldSigInt); err != nil { fmt.Printf("获取原SIGINT处理器失败: %v\n", err) } if err := syscall.Sigaction(syscall.SIGTERM, nil, &oldSigTerm); err != nil { fmt.Printf("获取原SIGTERM处理器失败: %v\n", err) } osSignalChannel := make(chan os.Signal, 1) signal.Notify(osSignalChannel, syscall.SIGINT, syscall.SIGTERM) resultCh := make(chan int, 1) defer func() { // 恢复原来的信号处理器,而不是重置成系统默认 if err := syscall.Sigaction(syscall.SIGINT, &oldSigInt, nil); err != nil { fmt.Printf("恢复SIGINT处理器失败: %v\n", err) } if err := syscall.Sigaction(syscall.SIGTERM, &oldSigTerm, nil); err != nil { fmt.Printf("恢复SIGTERM处理器失败: %v\n", err) } signal.Stop(osSignalChannel) close(osSignalChannel) close(resultCh) }() go func() { fmt.Println("执行长耗时任务中...") time.Sleep(5 * time.Minute) resultCh <- a + b }() for { select { case <-ctx.Done(): fmt.Println("任务超时") return 0 case returnSignal := <-osSignalChannel: fmt.Printf("收到中断信号: %v\n", returnSignal) return 0 case result := <-resultCh: fmt.Println("长耗时任务完成") return result } } } func main() { // 编译共享库必须要有这个函数,实际不会被调用 }
验证方法
- 用修改后的
main.go重新生成共享库:go build -o main.so -buildmode=c-shared main.go - 运行
python3 main.py,按Ctrl+C中断Go的长任务 - 到
input()/getpass()环节时,按Ctrl+C,此时会正常触发KeyboardInterrupt,Python程序直接退出,和预期一致
总结一下:通过保存并恢复进程原有的信号处理器(也就是Python设置的那个),Go函数退出后Python的信号逻辑就回到了原来的状态,自然就能正常响应Ctrl+C了。
内容的提问来源于stack exchange,提问作者nascarsayan
相关产品推荐
相关产品推荐

