在多线程环境中使用MPI_THREAD_SERIALIZED时,所有MPI调用都需要锁吗?

时间:2019-07-02 16:55:25

标签: c++ multithreading parallel-processing mpi

在MPI多线程环境中,当我们使用 MPI_THREAD_SERIALIZED 初始化 MPI_Init_thread 时,应使用互斥锁(或其他线程锁定机制)保护MPI调用(选中this answer) 。 MPI_THREAD_MULTIPLE 不需要这样做,但并非所有MPI实现都支持。

我的问题是,某些MPI功能(尤其是 MPI_Test MPI_Wait MPI_Get_count )是否绝对需要锁定。我知道所有“具有通信功能”的MPI调用(例如 MPI_Gather MPI_Bcast MPI_Send MPI_Recv MPI_Isend MPI_Irecv 等),但我怀疑其他功能(例如 MPI_Get_count )不需要此锁定,即局部功能。我需要知道 MPI_Test MPI_Wait MPI_Get_count MPI_Probe MPI_Iprobe (我不知道其中哪些是局部函数,哪些不是局部函数)。是在MPI标准中定义了这种锁依赖关系还是在实现中定义了?

我正在开发一个并行库,该库具有与C ++ 11线程混合的无阻塞MPI调用,并且我需要使用 MPI_THREAD_SERIALIZED 支持大多数MPI实现。库中还实现了 MPI_THREAD_MULTIPLE (在大多数情况下,性能更好),但是还需要 MPI_THREAD_SERIALIZED 支持。

在下一个简单的示例代码中,在 MPI_Test 调用之前是否需要锁定?

#include <mutex>
#include <vector>
#include <thread>
#include <iostream>
#include <mpi.h>

static std::mutex mutex;
const static int numThreads = 4;
static int rank;
static int nprocs;

static void rthread(const int thrId) {
    int recv_buff[2];
    int send_buff[2];
    MPI_Request recv_request;

    {
        std::lock_guard<std::mutex> lck(mutex);     // <-- this lock is required
        MPI_Irecv(recv_buff, 2, MPI_INT, ((rank>0) ? rank-1 : nprocs-1), thrId, MPI_COMM_WORLD, &recv_request);
    }

    send_buff[0] = thrId;
    send_buff[1] = rank;
    {
        std::lock_guard<std::mutex> lck(mutex);     // <-- this lock is required
        MPI_Send(send_buff, 2, MPI_BYTE, ((rank+1<nprocs) ? rank+1 : 0), thrId, MPI_COMM_WORLD);
    }

    int flag = 0;
    while (!flag) {
        std::lock_guard<std::mutex> lck(mutex);    // <-- is this lock required?
        MPI_Test(&recv_request, &flag, MPI_STATUS_IGNORE);
        //...        do other stuff
    }

    std::cout << "[Rank " << rank << "][Thread " << thrId << "] Received a msg from thread " << recv_buff[0] << " from rank " << recv_buff[1] << std::endl;

}

int main(int argc, char **argv) {
    int provided;

    MPI_Init_thread(&(argc), &(argv), MPI_THREAD_SERIALIZED, &provided);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);

    std::vector<std::thread> threads;
    for(int threadId = 0; threadId < numThreads; threadId++) {
        threads.push_back(std::thread(rthread, threadId));
    }
    for(int threadId = 0; threadId < numThreads; threadId++) {
        threads[threadId].join();
    }
    MPI_Finalize();
}

在测试中,我在 MPI_Test MPI_Get_count 调用中执行了一些没有锁的代码,没有发生任何不良情况,并且性能有所提高,但是我不知道这样做是否可以是否。

1 个答案:

答案 0 :(得分:1)

需要锁定。该标准只是简要说明:

  

MPI_THREAD_SERIALIZED :该过程可能是多线程的,并且有多个   线程可以进行MPI调用,但一次只能进行一次:MPI调用不是   由两个不同的线程同时进行

因此,对不同种类的MPI函数的调用之间没有区别。由于您打算编写可移植的代码-否则,您可以假设使用MPI_THREAD_MULTIPLE实现-您必须遵守标准。