MPI矩阵-向量乘法自调度程序移植后死锁问题求助
MPI矩阵-向量乘法自调度程序移植死锁问题
我将《Using MPI: Portable Parallel Programming with the Message-Passing Interface(第二版)》(William Gropp、Ewing Lusk、Anthony Skjellum著)第35页的Fortran版MPI矩阵-向量乘法自调度程序移植为C后,程序在首次发送操作后出现死锁。程序逻辑为:0号进程向所有进程广播向量,同时给每个进程发送一行矩阵;其他进程计算矩阵行与向量的点积后将结果返回给0号进程。移植后的C代码如下:
#include "mpi.h" #include <iostream> #include <vector> int main(){ int W_rank, W_size; int rows = 9; int cols = 9; std::vector<double> b(cols); std::vector< std::vector<double> > a(rows); std::vector<double> c(rows); std::vector<double> buffer(cols); for(auto& row : a) row.resize(cols); MPI_Init(NULL, NULL); MPI_Comm_size(MPI_COMM_WORLD, &W_size); MPI_Comm_rank(MPI_COMM_WORLD, &W_rank); if(W_rank == 0){ std::fill(b.begin(), b.end(), 1); for(auto& row : a){ std::fill(row.begin(), row.end(), 1); } int numsent = 0; MPI_Bcast(b.data(), b.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD); for(int i=0; i<W_size; ++i){ for(int j=0; j<cols; ++j){ buffer[j] = a[i][j]; } MPI_Send(buffer.data(), buffer.size(), MPI_DOUBLE, i, i, MPI_COMM_WORLD);///////deadlocks here/////////// ++numsent; } double ans; MPI_Status status; for(int i=0; i<rows; ++i){ MPI_Recv(&ans, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status); int sender = status.MPI_SOURCE; int tag = status.MPI_TAG; c[tag] = ans; if(numsent<rows){ for(int j=0; j<cols; ++j){ buffer[j] = a[numsent][j]; } MPI_Send(buffer.data(), buffer.size(), MPI_DOUBLE, sender, numsent, MPI_COMM_WORLD); ++numsent; } else{ MPI_Send(MPI_BOTTOM, 0, MPI_DOUBLE, sender, -1, MPI_COMM_WORLD); } } } else{ MPI_Bcast(b.data(), b.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Status status; while(1){ MPI_Recv(buffer.data(), buffer.size(), MPI_DOUBLE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);//////deadlocks here/////////////////// if(status.MPI_TAG == -1) break; int row = status.MPI_TAG; double ans = 0; for(int i=0; i<cols; ++i){ ans += buffer[i]*b[i]; } MPI_Send(&ans, 1, MPI_DOUBLE, 0, row, MPI_COMM_WORLD); } } MPI_Finalize(); return 0; }
死锁原因分析
死锁核心原因是0号进程给自己发送矩阵行消息,但自身无对应接收逻辑:
MPI_Send是阻塞调用,必须等接收方调用MPI_Recv接收消息后才会返回。- 0号进程给自身发送消息后,后续代码没有任何
MPI_Recv操作去接收该消息,导致MPI_Send一直阻塞,触发死锁。 - 原逻辑中0号进程无需通过消息传递给自己分配任务,直接本地计算即可。
修复后的代码
#include "mpi.h" #include <iostream> #include <vector> int main(){ int W_rank, W_size; int rows = 9; int cols = 9; std::vector<double> b(cols); std::vector<std::vector<double>> a(rows); std::vector<double> c(rows); std::vector<double> buffer(cols); for(auto& row : a) row.resize(cols); MPI_Init(NULL, NULL); MPI_Comm_size(MPI_COMM_WORLD, &W_size); MPI_Comm_rank(MPI_COMM_WORLD, &W_rank); if(W_rank == 0){ // 初始化数据 std::fill(b.begin(), b.end(), 1); for(auto& row : a){ std::fill(row.begin(), row.end(), 1); } int numsent = 0; // 广播向量b MPI_Bcast(b.data(), b.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD); // 初始仅给其他进程分配任务,跳过自身 for(int i=1; i<W_size; ++i){ if(numsent >= rows) break; for(int j=0; j<cols; ++j){ buffer[j] = a[numsent][j]; } MPI_Send(buffer.data(), buffer.size(), MPI_DOUBLE, i, numsent, MPI_COMM_WORLD); ++numsent; } // 本地计算第0行结果 c[0] = 0.0; for(int j=0; j<cols; ++j){ c[0] += a[0][j] * b[j]; } double ans; MPI_Status status; // 接收其余rows-1个结果 for(int i=1; i<rows; ++i){ MPI_Recv(&ans, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status); int sender = status.MPI_SOURCE; int tag = status.MPI_TAG; c[tag] = ans; if(numsent < rows){ for(int j=0; j<cols; ++j){ buffer[j] = a[numsent][j]; } MPI_Send(buffer.data(), buffer.size(), MPI_DOUBLE, sender, numsent, MPI_COMM_WORLD); ++numsent; } else{ // 发送终止信号 MPI_Send(MPI_BOTTOM, 0, MPI_DOUBLE, sender, -1, MPI_COMM_WORLD); } } // 确保所有进程收到终止信号,避免进程阻塞在接收循环 for(int i=1; i<W_size; ++i){ MPI_Probe(i, MPI_ANY_TAG, MPI_COMM_WORLD, &status); if(status.MPI_TAG != -1){ MPI_Recv(&ans, 1, MPI_DOUBLE, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status); } MPI_Send(MPI_BOTTOM, 0, MPI_DOUBLE, i, -1, MPI_COMM_WORLD); } } else{ // 接收广播的向量b MPI_Bcast(b.data(), b.size(), MPI_DOUBLE, 0, MPI_COMM_WORLD); MPI_Status status; while(true){ MPI_Recv(buffer.data(), buffer.size(), MPI_DOUBLE, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status); if(status.MPI_TAG == -1) break; int row = status.MPI_TAG; double ans = 0.0; for(int i=0; i<cols; ++i){ ans += buffer[i] * b[i]; } MPI_Send(&ans, 1, MPI_DOUBLE, 0, row, MPI_COMM_WORLD); } } MPI_Finalize(); return 0; }
关键修改点
- 移除0号进程自发送逻辑:初始任务分配仅给rank≥1的进程发送,避免无接收方的阻塞调用。
- 0号进程本地计算任务:直接计算第0行点积,无需通过消息传递。
- 调整接收循环次数:原循环接收rows次,改为接收rows-1次,因为自身已计算一行。
- 确保终止信号覆盖所有进程:接收完结果后,给每个进程发送终止信号,防止进程卡在接收循环。
内容的提问来源于stack exchange,提问作者Niccolò Tiezzi
相关产品推荐
相关产品推荐

