添加MPI Barrier后子进程print语句未执行的原因咨询
问题场景
使用mpi4py编写多进程程序时遇到异常:在子进程代码中添加comm.Barrier()后,程序挂起,且Barrier前的print("rank {}".format(rank))语句无输出。以下是修正了变量名错误和缺失导入的最小可复现代码mwe.py:
import numpy as np from mpi4py import MPI import time import itertools import sys N=8 comm = MPI.COMM_WORLD rank = comm.Get_rank() N_sub = comm.Get_size()-1 get_sub = itertools.cycle(range(1, N_sub+1)) if rank == 0: print("I am rank {}".format(rank)) data = [] for i in range(N): nums = np.random.normal(size=6) data.append(nums) sleep_short = 0.0001 sleep_long = 0.1 sleep_dict = {} for r in range(1, N_sub+1): sleep_dict[str(r)] = 1 data_idx = 0 while len(data) > data_idx: r = next(get_sub) if comm.iprobe(r): useless_info = comm.recv(source=r) print(useless_info) comm.send(data[data_idx], dest=r) data_idx += 1 print("data_idx {}".format(data_idx)) sleep_dict[str(r)] = 1 else: sleep_dict[str(r)] = 0 if all(value == 0 for value in sleep_dict.values()): time.sleep(sleep_long) else: time.sleep(sleep_short) for r in range(1, N_sub+1): comm.send('Done', dest=r) else: print("rank {}".format(rank)) ###################### # vvv 问题语句 ###################### comm.Barrier() while True: comm.send("I am done", dest=0) model = comm.recv(source=0) if type(model).__name__ == 'str': break MPI.Finalize() sys.exit(0)
运行命令:mpirun -np 4 python mwe.py
原本预期子进程先打印自身rank,再进入Barrier等待,但实际看不到子进程的print输出,且程序因Barrier阻塞挂起。
问题原因
Barrier的阻塞逻辑:MPI的
Barrier()要求通信子内所有进程都到达该调用点才能继续执行。你的代码中主进程(rank=0)从未调用Barrier(),它一直在数据分发循环中运行,不会进入等待。而子进程(rank1-3)执行到comm.Barrier()后就会无限阻塞,等待主进程也到达Barrier——这永远不会发生。标准输出缓冲:子进程的
print语句其实已经执行了,但Python的标准输出默认是行缓冲模式,在多进程环境中,缓冲区内容不会立即输出到终端。当子进程卡在Barrier时,缓冲区未被刷新,所以你看不到输出。并不是代码没执行,而是输出被暂存起来了。
验证与解决方法
验证print是否执行
将子进程的print改为写入文件,即使进程阻塞,文件也会记录内容:
else: with open(f"rank_{rank}_log.txt", "w") as f: f.write(f"rank {rank}\n") comm.Barrier() # 后续代码不变
运行后查看生成的文件,会发现子进程的rank已经被写入,证明print逻辑确实执行了。
修复Bar阻塞问题
如果要使用Barrier,必须让所有进程(包括主进程)都调用它。比如在主进程初始化数据后,分发循环前添加comm.Barrier():
if rank == 0: print("I am rank {}".format(rank)) data = [] for i in range(N): nums = np.random.normal(size=6) data.append(nums) comm.Barrier() # 主进程也调用Barrier # 后续分发逻辑不变
这样所有进程都会先执行print,再一起通过Barrier,继续后续流程。
强制刷新输出
如果想直接看到print的内容,在print时添加flush=True强制刷新缓冲区:
else: print("rank {}".format(rank), flush=True) comm.Barrier() # 后续代码不变
这样即使子进程后续阻塞,print的内容也会立即显示在终端。
内容的提问来源于stack exchange,提问作者gabe

