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

如何确保Python多进程调用Docker运行ROS2节点时可靠处理SIGINT

解决Python multiprocessing + Docker + ROS2节点的SIGINT(Ctrl-C)清理问题

问题根源

  • Docker的-it参数会为每个容器分配独立TTY,导致SIGINT信号只能传递到当前前台容器,后台容器无法收到终止信号。
  • multiprocessing子进程的信号处理与父进程隔离,父进程收到Ctrl-C中断join()后,子进程可能仍在运行,Docker容器未被正确终止。
  • 即使使用--init参数,TTY隔离依然会阻碍信号传递到容器内的ROS2节点。

解决方案

方案1:改进multiprocessing + subprocess的信号处理

修改原代码,解决TTY绑定和信号传递问题:

import subprocess
import multiprocessing
import signal
import os

def run_node(node):
    # 替换-it为-t,避免独立TTY导致的信号隔离;添加--rm确保容器退出即删除
    proc = subprocess.Popen(
        ["docker", "run", "-t", "--rm", "--init", "osrf/ros:foxy-desktop",
         "bash", "-c", f"ros2 run demo_nodes_py {node}"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    try:
        proc.wait()
    except KeyboardInterrupt:
        # 捕获中断信号,主动终止Docker进程
        proc.terminate()
        proc.wait()

def handle_sigint(signum, frame):
    # 父进程统一处理SIGINT,终止所有子进程
    for child_proc in multiprocessing.active_children():
        child_proc.terminate()
    # 等待所有子进程完全退出
    for child_proc in multiprocessing.active_children():
        child_proc.join()
    os._exit(0)

if __name__ == '__main__':
    # 注册SIGINT信号处理器,确保父进程优先捕获中断
    signal.signal(signal.SIGINT, handle_sigint)
    
    nodes = [
        multiprocessing.Process(target=run_node, args=(node,))
        for node in ["add_two_ints_server", "add_two_ints_client"]
    ]
    
    for node in nodes:
        node.start()
    
    for node in nodes:
        node.join()

关键改动说明:

  • 移除-it改用-t:避免每个容器占用独立TTY,让信号能通过父进程的subprocess传递到容器。
  • 使用subprocess.Popen替代subprocess.call:允许主动终止Docker进程,而非阻塞等待。
  • 父进程注册SIGINT处理器:统一管理所有子进程的终止,确保没有残留。
  • 添加--rm参数:即使容器异常退出,也会自动删除,避免无用容器残留。

方案2:使用Docker Compose统一管理容器

这种方案更简洁,让Docker Compose负责容器生命周期管理,Python只需要启动和停止Compose进程:

  1. 编写docker-compose.yml:
version: '3'
services:
  add_two_ints_server:
    image: osrf/ros:foxy-desktop
    command: bash -c "ros2 run demo_nodes_py add_two_ints_server"
    init: true
  add_two_ints_client:
    image: osrf/ros:foxy-desktop
    command: bash -c "ros2 run demo_nodes_py add_two_ints_client"
    init: true
    depends_on:
      - add_two_ints_server
  1. 编写Python控制代码:
import subprocess
import signal
import os

def handle_sigint(signum, frame):
    # 收到Ctrl-C时,调用docker-compose down终止所有容器
    subprocess.run(["docker-compose", "down"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    os._exit(0)

if __name__ == '__main__':
    signal.signal(signal.SIGINT, handle_sigint)
    # 启动所有容器
    compose_proc = subprocess.Popen(["docker-compose", "up"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    compose_proc.wait()

优势:

  • Docker Compose会统一处理所有容器的信号传递和终止逻辑,无需手动管理多个进程。
  • 避免multiprocessing带来的信号隔离问题,代码更简洁可靠。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:14:57