树莓派运行Python多进程TCP服务调用kill()无法终止子进程问题
树莓派Python multiprocessing子进程kill()跨平台失效问题解决方案
问题背景
- 基于树莓派自研TCP服务端LED控制器,通过接收TCP报文切换预设灯光场景
- 所有灯光场景为无限while循环逻辑,采用Python
multiprocessing模块创建独立子进程运行场景 - 设计逻辑:收到新的场景切换指令时,终止当前运行的场景子进程,再启动新场景对应子进程
- 异常表现:同一套脚本在Windows下运行正常,
Process.kill()可正常终止子进程;迁移到树莓派(Linux环境)后,kill()方法无法按预期终止运行中的子进程,功能完全失效
测试代码
服务端脚本
import multiprocessing import time from time import sleep try: from analyse import * from handler import * from history import writeState from led import * except: pass import socket from subprocess import check_output # 读取Raspbian系统IP地址 from platform import system class SYSINFO(): os=system() # 识别当前运行的操作系统类型 if os=="Linux": IP_ADDRESS=check_output(["hostname",'-I']).decode().split(" ")[0] elif os=="Windows" or os=="Darwin": IP_ADDRESS= socket.gethostbyname(socket.gethostname()) class MyProcess(multiprocessing.Process): def __init__(self, ): multiprocessing.Process.__init__(self) self.exit = multiprocessing.Event() def run(self): while not self.exit.is_set(): print(round(time.perf_counter()), self.pid) time.sleep(1) print("进程正常退出") def shutdown(self): print("触发优雅停机") self.exit.set() class TCPController(multiprocessing.Process): def __init__(self, ): multiprocessing.Process.__init__(self) self.exit = multiprocessing.Event() def run(self): counter=0 def shutdown(self): print("触发优雅停机") self.exit.set() if __name__ == "__main__": HOST = SYSINFO.IP_ADDRESS PORT = 6060 while True: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((HOST, PORT)) print(f"服务端已启动,监听地址 {HOST!r} , 端口 {PORT!r}") s.listen() while True: try: conn, addr = s.accept() print("等待连接") with conn: print(f"收到连接来自 {addr}") data = conn.recv(1024).decode() print(data) if data=="on": process = MyProcess() process.daemon=True process.start() time.sleep(3) elif data=="off": process.kill() time.sleep(3) print("子进程状态: %d" % process.is_alive()) except: pass sleep(.5)
客户端循环测试脚本
# echo-client.py import socket from time import sleep class heimkinoSteuereinheit: HOST = "192.168.168.97" PORT = 6060 def cinemaclient(msg): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((heimkinoSteuereinheit.HOST, heimkinoSteuereinheit.PORT)) s.sendall(msg.encode()) data = s.recv(1024).decode() print(f"收到服务端返回 {data!r}") return data while True: sleep(1) cinemaclient("on") sleep(5) cinemaclient("off")
问题根因
- Windows和Linux下Python
multiprocessing默认进程启动模式不同:Windows默认使用spawn模式,子进程是完全独立的新进程,Process.kill()调用系统TerminateProcess接口可直接终止进程;Linux(含树莓派Raspbian系统)默认使用fork模式,子进程会完整复制父进程内存空间,当子进程存在阻塞IO、C扩展层面死循环、信号屏蔽逻辑时,kill()发送的SIGTERM信号会被阻塞,无法终止进程。 - 原代码存在作用域bug:
process对象仅在收到on指令的分支内定义,若服务端启动后先收到off指令会直接触发变量不存在报错;多次发送on指令会覆盖进程引用,导致之前启动的子进程成为孤儿进程,完全失去管控。 - 原代码调用
kill()后未执行join()回收进程资源,Linux下会产生大量僵尸进程,占用PID资源最终导致新进程无法启动。
修复方案
1. 强制对齐跨平台进程启动模式
在主程序入口第一行添加配置,让Linux下子进程启动逻辑和Windows完全一致,从根源消除系统差异:
if __name__ == "__main__": multiprocessing.set_start_method('spawn', force=True) # 保留后续原有逻辑
2. 维护全局进程引用,补全资源回收逻辑
新增全局变量存储当前运行的场景进程,每次启动新进程前先清理旧进程,终止进程后必须调用join()回收资源:
if __name__ == "__main__": multiprocessing.set_start_method('spawn', force=True) current_process = None # 全局维护当前运行的场景进程句柄 HOST = SYSINFO.IP_ADDRESS PORT = 6060 # 保留原有socket初始化逻辑 # 指令处理分支修改为: if data == "on": # 启动新进程前先终止残留的旧进程 if current_process and current_process.is_alive(): current_process.kill() current_process.join() current_process = MyProcess() current_process.daemon = True current_process.start() time.sleep(3) elif data == "off": if current_process and current_process.is_alive(): current_process.kill() current_process.join() time.sleep(3) print(f"子进程状态: {current_process.is_alive() if current_process else '无运行中的进程'}")
3. 优先使用优雅停机,强杀作为兜底逻辑
代码中已经实现的shutdown()方法通过Event通知子进程退出是更安全的方案,不会导致LED硬件因为进程被强杀出现IO口未复位、灯珠常亮等异常,可封装统一的进程停止函数:
def stop_running_process(proc, timeout=2): if not proc or not proc.is_alive(): return # 先尝试发信号让进程自行退出 proc.shutdown() proc.join(timeout=timeout) # 超时未退出再强制杀死 if proc.is_alive(): proc.kill() proc.join()
所有需要终止场景进程的位置直接调用该函数即可,兼顾跨平台兼容性和硬件安全性。
内容的提问来源于stack exchange,提问作者WNZL
相关产品推荐
相关产品推荐

