MPI中Scatter与Ibcast组合使用引发死锁问题求助
MPI Scatter/Reduce 结合非阻塞Ibcast的死锁问题解决
问题根源分析
你的代码出现死锁的核心原因是进程0的阻塞MPI_Reduce与非阻塞MPI_Ibcast的执行顺序冲突:
- 所有进程在调用
MPI_Ibcast后立即进入阻塞的MPI_Reduce。进程0作为MPI_Reduce的根进程,需要等待所有其他进程发送本地结果才能完成Reduce操作; - 但其他进程的
MPI_Ibcast接收操作需要进程0完成广播的发送逻辑,而进程0此时被MPI_Reduce阻塞,无法处理Ibcast的发送请求; - 这就形成了循环等待:其他进程等Ibcast完成,进程0等Reduce完成,最终导致死锁。
解决方案
根据你的需求(求和完成后发送广播,或让第一个完成的进程发起广播),提供两种可行方案:
方案1:求和完成后再发起广播
将广播操作移至Reduce完成后,确保进程0在处理完求和后再执行广播发送,避免阻塞冲突。
修改后的代码:
#include<iostream> #include <algorithm> #include <memory> #include <random> #include "mpi.h" int main(int argc, char *argv[]){ MPI_Init(&argc, &argv); int num_proc; int task_id; MPI_Comm_rank(MPI_COMM_WORLD, &task_id); MPI_Comm_size(MPI_COMM_WORLD, &num_proc); const int num_elements = 1 << 10; const int chunk_size = num_elements / num_proc; int local_buffer; MPI_Request req; std::unique_ptr<int[]> send_ptr; if(task_id == 0){ local_buffer = 42; send_ptr = std::make_unique<int []>(num_elements); std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist(1, 1); std::generate(send_ptr.get(), send_ptr.get() + num_elements, [&] {return dist(mt);}); } std::cout << "Processor : " << task_id << " declares : " << local_buffer << std::endl; auto recv_buffer = std::make_unique<int []>(chunk_size); std::cout << "Before scatter" << std::endl; MPI_Scatter(send_ptr.get(), chunk_size, MPI_INT, recv_buffer.get(), chunk_size, MPI_INT, 0, MPI_COMM_WORLD); std::cout << "After scatter" << std::endl; int local_result = 0; for(int i = 0; i < chunk_size; i++){ std::cout << "Processor: " << task_id << " completion: "<< ((float)i/chunk_size)*100 << "%\n"; local_result += recv_buffer[i]; } int global_result; MPI_Reduce(&local_result, &global_result, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD); if (task_id == 0){ std::cout << "global result : " << global_result << "\n"; } // 求和完成后发起非阻塞广播 MPI_Ibcast(&local_buffer, 1, MPI_INT, 0, MPI_COMM_WORLD, &req); MPI_Wait(&req, MPI_STATUS_IGNORE); std::cout << "Processor : " << task_id << " receives the value : " << local_buffer << std::endl; MPI_Finalize(); return 0; }
方案2:让第一个完成本地计算的进程发起广播
如果需要让最早完成本地求和的进程触发广播,可以结合MPI_Test或非阻塞Reduce来实现:
核心思路:
- 所有进程完成本地计算后,启动非阻塞Reduce(
MPI_Ireduce); - 循环检测Reduce完成状态,一旦本地计算完成,通过
MPI_Allgather等方式选举第一个完成的进程作为广播根; - 所有进程参与该根进程发起的广播,再等待Reduce完成。
关键代码片段:
// 本地计算完成后 int local_result = 0; for(int i = 0; i < chunk_size; i++){ local_result += recv_buffer[i]; } // 启动非阻塞Reduce MPI_Request reduce_req; int global_result; MPI_Ireduce(&local_result, &global_result, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD, &reduce_req); // 模拟选举第一个完成的进程(实际可通过时间戳+Allgather实现) int completion_flag = 1; int all_flags[num_proc]; MPI_Allgather(&completion_flag, 1, MPI_INT, all_flags, 1, MPI_INT, MPI_COMM_WORLD); int root = 0; for(int i=0; i<num_proc; i++){ if(all_flags[i] == 1){ root = i; break; } } // 自定义广播值 if(task_id == root){ local_buffer = 99; } // 参与非阻塞广播 MPI_Request bcast_req; MPI_Ibcast(&local_buffer, 1, MPI_INT, root, MPI_COMM_WORLD, &bcast_req); MPI_Wait(&bcast_req, MPI_STATUS_IGNORE); // 等待Reduce完成 MPI_Wait(&reduce_req, MPI_STATUS_IGNORE); if(task_id == 0){ std::cout << "global result : " << global_result << "\n"; }
关键注意事项
- 非阻塞操作的发送端不能被阻塞:非阻塞广播的根进程必须有足够的时间处理发送逻辑,不能被其他阻塞MPI调用卡住;
- 操作顺序优先级:如果依赖某个操作的结果再执行广播,必须确保依赖操作完成后再发起广播;
- 清理冗余变量:原代码中
num_tasks和num_proc重复定义,建议清理以提高可读性。
内容的提问来源于stack exchange,提问作者Stelfano
相关产品推荐
相关产品推荐

