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

Python多线程程序无法通过Ctrl+C终止问题求助

解决单次Ctrl+C终止所有线程的方案

你的问题根源在于:虽然线程被设为daemon=True,但线程内的无限循环没有响应中断的逻辑,且主线程在join()时阻塞,导致首次按下Ctrl+C后,线程可能卡在time.sleep、外部脚本执行等阻塞操作中,无法立刻终止,需要再次触发中断才能强制结束。

要实现单次Ctrl+C就终止所有线程,我们可以用线程安全的终止信号(threading.Event),让每个线程主动检查终止信号,同时主线程捕获中断后触发信号,等待线程优雅退出。

具体修改步骤:

  1. 创建全局的stop_event对象,用于传递终止信号
  2. 把线程函数的while True循环改为while not stop_event.is_set(),让线程主动检查终止信号
  3. 用stop_event.wait(timeout)替代time.sleep,这样线程在等待期间能及时响应终止信号
  4. 替换os.popen为subprocess.run并设置超时,避免外部脚本长时间运行导致线程卡死
  5. 主线程捕获KeyboardInterrupt异常,触发stop_event后等待所有线程退出

修改后的完整代码:

import threading
import datetime
import time
import subprocess
import logging

# 初始化线程安全的终止信号
stop_event = threading.Event()

def calculate_statistics(ip_slave, path_script_slave, logger):
    # 循环检查终止信号
    while not stop_event.is_set():
        current_time = datetime.datetime.now()
        # 用subprocess替代os.popen,添加超时避免线程卡死
        try:
            result = subprocess.run(
                ["perl", f"{path_script_slave}SYSTEMCMD.pl", "uf_rxinfo", "all"],
                capture_output=True,
                text=True,
                timeout=0.1
            ).stdout
        except subprocess.TimeoutExpired:
            logger.warning(f"[{current_time}][SLAVE][{ip_slave}] 执行perl脚本超时")
            stop_event.wait(0.2)
            continue
        
        # 优化结果解析逻辑,避免多次重复split
        parts = result.split(',')
        if len(parts) < 6:
            logger.warning(f"[{current_time}][SLAVE][{ip_slave}] 结果格式错误: {result}")
            stop_event.wait(0.2)
            continue
        
        rssi = parts[1]
        agc = parts[3]
        auth = parts[5]
        
        log_content = f"[{current_time}][SLAVE][{ip_slave}] RSSI: {rssi}; AGC: {agc}; AUTH: {auth}."
        print(log_content)
        logger.info(f"{current_time} -IP {ip_slave} -RSSI {rssi} -AGC {agc} -AUTH {auth}")
        
        # 用stop_event.wait替代time.sleep,响应终止信号
        stop_event.wait(0.2)

def computing(f_name):
    # 初始等待时检查终止信号
    if stop_event.wait(1):
        return
    while not stop_event.is_set():
        # 用with语句自动管理文件,避免资源泄漏
        try:
            with open("slaves.log", "r") as a_file:
                lines = a_file.readlines()
            last_lines = lines[-8:] if len(lines)>=8 else lines
            
            rssi_slave_1 = []
            rssi_slave_2 = []
            agc_slave_1 = []
            agc_slave_2 = []
            auth_slave_1 = []
            auth_slave_2 = []
            
            with open("diff.log", "w") as f:
                for element in last_lines:
                    f.write(element)
                    # 优化行解析逻辑,减少重复split操作
                    element_parts = element.strip().split(" -")
                    ip = next(p.split()[1] for p in element_parts if p.startswith("IP"))
                    rssi = next(p.split()[1] for p in element_parts if p.startswith("RSSI"))
                    agc = next(p.split()[1] for p in element_parts if p.startswith("AGC"))
                    auth = next(p.split()[1] for p in element_parts if p.startswith("AUTH"))
                    
                    if ip == str(ip_slave_1):
                        rssi_slave_1.append(rssi)
                        agc_slave_1.append(agc)
                        auth_slave_1.append(auth)
                    else:
                        rssi_slave_2.append(rssi)
                        agc_slave_2.append(agc)
                        auth_slave_2.append(auth)
            
            print(rssi_slave_1, rssi_slave_2, agc_slave_1, agc_slave_2, auth_slave_1, auth_slave_2, sep='\n')
            algorithm(rssi_slave_1,rssi_slave_2,agc_slave_1,agc_slave_2,auth_slave_1,auth_slave_2)
            
        except Exception as e:
            print(f"computing线程出错: {e}")
        
        # 用stop_event.wait替代time.sleep
        stop_event.wait(1)

# 假设此处已定义ip_slave_1、ip_slave_2、path_script_slave_1、path_script_slave_2、algorithm
# 初始化日志示例
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.FileHandler("slaves.log")
logger.addHandler(handler)

if __name__ == "__main__":
    # 注意args参数末尾加逗号,避免字符串被拆分为单个字符传递
    t1 = threading.Thread(target=calculate_statistics, args=(ip_slave_1, path_script_slave_1, logger))
    t2 = threading.Thread(target=calculate_statistics, args=(ip_slave_2, path_script_slave_2, logger))
    t3 = threading.Thread(target=computing, args=("1",))
    
    t1.daemon = True
    t2.daemon = True
    t3.daemon = True
    
    t1.start()
    t2.start()
    t3.start()
    
    try:
        t1.join()
        t2.join()
        t3.join()
    except KeyboardInterrupt:
        print("\n收到终止信号,正在停止所有线程...")
        stop_event.set()
        # 等待线程完成退出逻辑
        t1.join()
        t2.join()
        t3.join()
        print("所有线程已终止")

关键说明:

  • stop_event.is_set()是线程安全的,多个线程可同时检查该标志
  • stop_event.wait(timeout)替代time.sleep后,线程在等待期间会随时响应终止信号,不会一直阻塞到sleep结束
  • subprocess.run加超时设置,避免外部perl脚本运行过久导致线程无法响应中断
  • 捕获KeyboardInterrupt后主动触发终止信号,让所有线程退出循环,实现优雅终止

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 02:06:22