You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

加载含信号处理器的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() {
    // 编译共享库必须要有这个函数,实际不会被调用
}

验证方法

  1. 用修改后的main.go重新生成共享库:go build -o main.so -buildmode=c-shared main.go
  2. 运行python3 main.py,按Ctrl+C中断Go的长任务
  3. 到input()/getpass()环节时,按Ctrl+C,此时会正常触发KeyboardInterrupt,Python程序直接退出,和预期一致

总结一下:通过保存并恢复进程原有的信号处理器(也就是Python设置的那个),Go函数退出后Python的信号逻辑就回到了原来的状态,自然就能正常响应Ctrl+C了。

内容的提问来源于stack exchange,提问作者nascarsayan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 00:45:55