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

Python多进程程序按Ctrl+C中断后命令提示符丢失的原因

问题原因与解决方案

核心原因

  1. 信号广播机制:按下Ctrl+C时,系统会向当前进程组的所有进程发送SIGINT中断信号,包括主进程和Pool中的子进程。主进程触发KeyboardInterrupt异常,但子进程可能未被正确终止,仍在向终端输出内容,干扰命令提示符的正常显示。
  2. 阻塞调用的局限性:p.map()是阻塞式调用,主进程会一直等待所有子进程执行完毕。此时收到中断信号,主进程抛出异常,但Pool的默认处理逻辑不会主动终止子进程,导致子进程继续运行,终端状态混乱。
  3. Windows终端兼容性:Windows命令提示符(cmd)对Python进程异常退出的处理存在缺陷,若进程未正确清理终端输入输出状态,会导致提示符丢失、输入不回显等问题。

修复方案

方案1:捕获中断信号并主动终止子进程

修改代码,注册信号处理器,收到Ctrl+C时立即终止所有子进程并清理资源:

from multiprocessing import Pool
import os
import time
import signal

def proc(name):
    print(f'\nStarting process name: {name} PID: {os.getpid()}')
    for i in range(1,5):
        print(f'{os.getpid()} : {i}\n')
        time.sleep(1)

def handle_interrupt(signum, frame):
    print('\nReceived Ctrl+C, terminating all processes...')
    global pool
    pool.terminate()  # 立即终止所有子进程
    pool.join()       # 等待子进程退出
    exit(0)

if __name__ == '__main__':
    print(f'\nMain process ID: {os.getpid()}\n')
    numServers = 3
    serverNum = list(range(1, numServers+1))
    print(f'serverNum: {serverNum}')
    
    # 注册SIGINT信号处理器
    signal.signal(signal.SIGINT, handle_interrupt)
    
    with Pool(numServers) as pool:
        try:
            pool.map(proc, serverNum)
        except KeyboardInterrupt:
            # 双重保险,捕获异常后兜底处理
            print('\nKeyboardInterrupt caught, cleaning up...')
    
    print(f'Ending mainline task {os.getpid()}')

方案2:使用非阻塞调用灵活控制进程

改用apply_async实现非阻塞提交,更灵活地处理中断:

from multiprocessing import Pool
import os
import time
import signal

def proc(name):
    print(f'\nStarting process name: {name} PID: {os.getpid()}')
    for i in range(1,5):
        print(f'{os.getpid()} : {i}\n')
        time.sleep(1)

def handle_interrupt(signum, frame):
    print('\nTerminating all processes...')
    global pool
    pool.terminate()
    pool.join()
    exit(0)

if __name__ == '__main__':
    print(f'\nMain process ID: {os.getpid()}\n')
    numServers = 3
    serverNum = list(range(1, numServers+1))
    print(f'serverNum: {serverNum}')
    
    signal.signal(signal.SIGINT, handle_interrupt)
    
    with Pool(numServers) as pool:
        # 非阻塞提交任务
        results = [pool.apply_async(proc, (name,)) for name in serverNum]
        try:
            # 等待所有任务完成
            for res in results:
                res.get()
        except KeyboardInterrupt:
            print('\nCleaning up processes...')
    
    print(f'Ending mainline task {os.getpid()}')

关键注意点

  • 用terminate()而非close():close()仅禁止提交新任务,会等待现有任务完成;terminate()会立即终止所有子进程,符合中断预期。
  • 信号处理器需全局引用Pool:确保在中断时能调用Pool的终止方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 07:18:20