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

Are locks required for all MPI calls when using MPI_THREAD_SERIALIZED in multithread enviroment?

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

我的问题是某些MPI函数是否绝对需要锁,特别是MPI_TestMPI_WaitMPI_Get_count。我知道所有 MPI 调用都需要锁定 "with communication"(例如 MPI_GatherMPI_BcastMPI_Send, MPI_Recv, MPI_Isend, MPI_Irecv, etc), 但是我怀疑其他函数不需要这个锁,比如MPI_Get_count, 即一个局部函数。我需要知道 MPI_TestMPI_Wait、[=23= 等函数是否需要此锁]MPI_Get_count、MPI_ProbeMPI_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_TestMPI_Get_count 调用中执行了一些没有锁定的代码,没什么不好的发生了并且性能提高了,但我不知道这是否可以。

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

MPI_THREAD_SERIALIZED The process may be multi-threaded, and multiple threads may make MPI calls, but only one at a time: MPI calls are not made concurrently from two distinct threads

所以对不同种类的MPI函数的调用没有区别。由于您的目标是编写可移植代码 - 否则您可以假设使用 MPI_THREAD_MULTIPLE 实现 - 您必须坚持标准。