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

树莓派运行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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 06:48:29