You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

关键修改点

  1. 移除0号进程自发送逻辑:初始任务分配仅给rank≥1的进程发送,避免无接收方的阻塞调用。
  2. 0号进程本地计算任务:直接计算第0行点积,无需通过消息传递。
  3. 调整接收循环次数:原循环接收rows次,改为接收rows-1次,因为自身已计算一行。
  4. 确保终止信号覆盖所有进程:接收完结果后,给每个进程发送终止信号,防止进程卡在接收循环。

内容的提问来源于stack exchange,提问作者Niccolò Tiezzi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 22:35:01