MPI单进程向自身收发大数组触发死锁问题
死锁原因
MPI的阻塞发送接口MPI_Send没有强制的返回规则,实际行为由消息大小和MPI实现的内置阈值决定:
- 当消息大小小于MPI实现预设的eager模式阈值时,MPI会直接将消息拷贝到库内部预留的急送缓冲区,
MPI_Send不需要等待接收端发起接收就可以直接返回。你测试N=10000时,数组总大小为10000 * 8Byte = 80KB,落在多数MPI实现默认的eager阈值范围内,Send执行完直接返回,后续MPI_Recv可以正常从内部缓冲区取走数据,因此程序能正常运行。 - 当消息大小超过eager阈值时,MPI不会为这类大消息分配内部缓冲,
MPI_Send会一直阻塞,直到匹配的接收操作被发起、完成消息握手和数据传输后才会返回。你测试N=100000时,数组总大小为100000 * 8Byte = 800KB,远超默认阈值,此时代码先调用MPI_Send阻塞等待接收端就绪,但单进程下MPI_Recv要等MPI_Send返回才能执行,两边循环等待就触发了死锁。
解决方案
你可以任选以下一种标准MPI写法修复问题,可适配所有消息大小:
- 使用MPI专门为避免收发顺序死锁设计的
MPI_Sendrecv接口,一次性绑定发送和接收操作,由MPI库内部调度执行顺序,直接替换原有Send、Recv两行代码即可:
// 替换原来的MPI_Send和MPI_Recv调用 MPI_Sendrecv(A, N, MPI_DOUBLE, 0, tag1, B, N, MPI_DOUBLE, 0, tag1, MPI_COMM_WORLD, &stat);
- 使用非阻塞通信接口,先提交收发请求再统一等待完成,不会出现阻塞互等的问题:
MPI_Request req[2]; // 提交非阻塞收发请求,调用后不会阻塞程序执行 MPI_Isend(A, N, MPI_DOUBLE, 0, tag1, MPI_COMM_WORLD, &req[0]); MPI_Irecv(B, N, MPI_DOUBLE, 0, tag1, MPI_COMM_WORLD, &req[1]); // 等待两个通信操作全部完成 MPI_Waitall(2, req, MPI_STATUSES_IGNORE);
- 如果你的场景固定是单进程内的数据复制,也可以直接用
memcpy(B, A, N*sizeof(double))替代MPI通信,性能更高,但这种写法无法兼容多进程跨rank通信的场景。
内容的提问来源于stack exchange,提问作者Mac cchiatooo
相关产品推荐
相关产品推荐

