Python多线程程序无法通过Ctrl+C终止问题求助
解决单次Ctrl+C终止所有线程的方案
你的问题根源在于:虽然线程被设为daemon=True,但线程内的无限循环没有响应中断的逻辑,且主线程在join()时阻塞,导致首次按下Ctrl+C后,线程可能卡在time.sleep、外部脚本执行等阻塞操作中,无法立刻终止,需要再次触发中断才能强制结束。
要实现单次Ctrl+C就终止所有线程,我们可以用线程安全的终止信号(threading.Event),让每个线程主动检查终止信号,同时主线程捕获中断后触发信号,等待线程优雅退出。
具体修改步骤:
- 创建全局的
stop_event对象,用于传递终止信号 - 把线程函数的
while True循环改为while not stop_event.is_set(),让线程主动检查终止信号 - 用
stop_event.wait(timeout)替代time.sleep,这样线程在等待期间能及时响应终止信号 - 替换
os.popen为subprocess.run并设置超时,避免外部脚本长时间运行导致线程卡死 - 主线程捕获
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
相关产品推荐
相关产品推荐

