
本文介绍在 Python 多进程场景下,如何安全、可靠地让一个进程主动终止另一个进程中启动的 subprocess.Popen 实例,避免因竞态条件导致的 AttributeError: 'NoneType' object has no attribute 'terminate' 错误。
本文介绍在 python 多进程场景下,如何安全、可靠地让一个进程主动终止另一个进程中启动的 subprocess.popen 实例,避免因竞态条件导致的 attributeerror: 'nonetype' object has no attribute 'terminate' 错误。
在使用 multiprocessing 启动多个独立进程(如 Blender 渲染 + nvidia-smi 监控)时,常见的误区是依赖全局变量(如 nvidia_cmd = None)跨进程共享子进程句柄。但需明确:Python 中的全局变量在不同 multiprocessing.Process 间完全隔离,父进程或子进程对全局变量的修改不会自动同步到其他进程内存空间中。 因此,run_blender 中访问的 nvidia_cmd 始终为初始值 None,而 run_nvidia 中对其的赋值仅影响自身进程副本——这正是 AttributeError 的根本原因。
要实现跨进程协同控制,必须使用进程间通信(IPC)机制。推荐采用 multiprocessing.Queue(线程/进程安全、轻量、阻塞可控),而非全局变量或 multiprocessing.Manager 等更重方案。以下是完整、健壮的实现:
import multiprocessing
import subprocess
import time
# 命令配置(请按实际路径调整)
BLENDER_CMD = '/home/mickey/dev/blender-4.2.1-linux-x64/blender -b /home/mickey/dev/elastic_shared_memory/benchmark_scenes/bmw27/bmw27.blend -f 10 -- --cycles-device CUDA'
NVIDIA_CMD = 'nvidia-smi --query-gpu=gpu_bus_id,memory.used --format=csv -l 1'
def run_blender(queue):
"""启动 Blender 并在完成时向监控进程发送终止信号"""
print("[Blender] 开始渲染...")
blender_proc = subprocess.Popen(BLENDER_CMD, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# 实时输出日志
for line in iter(blender_proc.stdout.readline, b''):
print(f"[Blender] {line.decode().strip()}")
blender_proc.wait() # 等待渲染结束
print("[Blender] 渲染完成,通知 GPU 监控进程终止...")
queue.put("TERMINATE") # 发送终止指令
def run_nvidia(queue):
"""启动 nvidia-smi 监控,并监听终止信号"""
print("[nvidia-smi] 启动 GPU 监控...")
nvidia_proc = subprocess.Popen(NVIDIA_CMD, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
try:
# 持续读取输出,同时检查终止信号
while nvidia_proc.poll() is None: # 进程仍在运行
# 非阻塞检查队列(避免卡死)
if not queue.empty():
signal = queue.get_nowait()
if signal == "TERMINATE":
print("[nvidia-smi] 收到终止信号,正在停止...")
nvidia_proc.terminate()
nvidia_proc.wait(timeout=3) # 最多等待3秒
break
# 读取一行输出(带超时避免永久阻塞)
try:
line = nvidia_proc.stdout.readline(timeout=0.5)
if line:
print(f"[nvidia-smi] {line.decode().strip()}")
except subprocess.TimeoutExpired:
continue
except Exception as e:
print(f"[nvidia-smi] 执行异常: {e}")
finally:
# 确保进程已清理
if nvidia_proc.poll() is None:
nvidia_proc.kill()
print("[nvidia-smi] 监控进程已退出")
if __name__ == '__main__':
# 创建 IPC 队列
control_queue = multiprocessing.Queue()
# 启动两个进程,传入队列
blender_p = multiprocessing.Process(target=run_blender, args=(control_queue,))
nvidia_p = multiprocessing.Process(target=run_nvidia, args=(control_queue,))
# 先启动监控,再启动渲染(确保监控就绪)
nvidia_p.start()
time.sleep(1) # 留出初始化时间(可选,增强鲁棒性)
blender_p.start()
# 等待两者结束
blender_p.join()
nvidia_p.join()
print("所有进程执行完毕。")
关键注意事项:
- ✅ 永远不要依赖跨进程全局变量:
multiprocessing中每个子进程拥有独立内存空间,全局变量不共享。 - ✅ 使用
queue.get_nowait()或带超时的get(timeout=...):避免在无信号时无限阻塞,影响日志实时性。 - ✅ 始终处理
subprocess的生命周期:调用terminate()后建议wait()或kill()确保资源释放;使用try/finally保证异常时也能清理。 - ✅ 启动顺序与同步:示例中先启
nvidia_p并延时,确保其Popen完成后再由blender_p发送信号,消除竞态。若需更强同步,可配合multiprocessing.Event。
该方案简洁、可移植、符合 Python 最佳实践,适用于任何需要跨进程协调子进程生命周期的场景。










