c++ - 使用使用 std::async 创建的线程发送 MPI 的线程安全

标签 c++ multithreading c++11 thread-safety mpi

根据this websiteMPI::COMM_WORLD.Send(...)的用法是线程安全的。但是,在我的应用程序中,我经常(并非总是)遇到死锁或出现段错误。附上 MPI::COMM_WORLD 的每个电话带有 mutex.lock() 的方法和 mutex.unlock()始终如一地消除死锁和段错误。

这就是我创建线程的方式:

const auto communicator = std::make_shared<Communicator>();
std::vector<std::future<size_t>> handles;
for ( size_t i = 0; i < n; ++i )
{
   handles.push_back(std::async(std::launch::async, foo, communicator));
}
for ( size_t i = 0; i < n; ++i )
{
   handles[i].get();
}
Communicator是一个具有 std::mutex 的类成员并独占调用诸如 MPI::COMM_WORLD.Send() 之类的方法和 MPI::COMM_WORLD.Recv() .我不使用任何其他通过 MPI 发送/接收的方法。 foo需要一个 const std::shared_ptr<Commmunicator> &作为论据。

我的问题:MPI promise 的线程安全与 std::async 创建的线程不兼容吗? ?

最佳答案

MPI 中的线程安全不是开箱即用的。首先,您必须确保您的实现实际上支持多个线程同时进行 MPI 调用。对于某些 MPI 实现,例如 Open MPI,这需要在构建时使用特殊选项配置库。然后您必须告诉 MPI 在适当的线程支持级别进行初始化。目前 MPI 标准定义了四个级别的线程支持:

  • MPI_THREAD_SINGLE - 表示用户代码是单线程的。如果 MPI_Init(),这是 MPI 初始化的默认级别用来;
  • MPI_THREAD_FUNNELED - 表示用户代码是多线程的,但只有主线程进行 MPI 调用。主线程是初始化 MPI 库的线程;
  • MPI_THREAD_SERIALIZED - 表示用户代码是多线程的,但对 MPI 库的调用是序列化的;
  • MPI_THREAD_MULTIPLE - 意味着用户代码是多线程的,所有线程都可以随时进行 MPI 调用,而无需进行任何同步。

  • 为了使用线程支持初始化 MPI,必须使用 MPI_Init_thread()而不是 MPI_Init() :
    int provided;
    
    MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided);
    if (provided < MPI_THREAD_MULTIPLE)
    {
        printf("ERROR: The MPI library does not have full thread support\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }
    

    等效的代码已过时 (并从 MPI-3 中删除)C++ 绑定(bind):
    int provided = MPI::Init_thread(argc, argv, MPI::THREAD_MULTIPLE);
    if (provided < MPI::THREAD_MULTIPLE)
    {
        printf("ERROR: The MPI library does not have full thread support\n");
        MPI::COMM_WORLD.Abort(1);
    }
    

    线程支持级别的顺序如下:MPI_THREAD_SINGLE < MPI_THREAD_FUNNELED < MPI_THREAD_SERIALIZED < MPI_THREAD_MULTIPLE ,所以任何其他提供的级别,不同于 MPI_THREAD_MULTIPLE会有较低的数值 - 这就是 if (...) 的原因上面的代码是这样写的。
    MPI_Init(&argc, &argv)相当于 MPI_Init_thread(&argc, &argv, MPI_THREAD_SINGLE, &provided) .实现不需要在请求的级别精确初始化——而是可以在任何其他级别(更高或更低)初始化,这在 provided 中返回。输出参数。

    有关更多信息 - 请参阅 MPI 标准的 §12.4,免费提供 here .

    对于大多数 MPI 实现,线程支持级别 MPI_THREAD_SINGLE实际上相当于在级别 MPI_THREAD_SERIALIZED 提供的- 正是你在你的情况下观察到的。

    由于您尚未指定您使用的 MPI 实现,这里有一个方便的列表。

    我已经说过 Open MPI 必须在启用正确标志的情况下编译才能支持 MPI_THREAD_MULTIPLE .但还有一个问题——它的 InfiniBand 组件不是线程安全的,因此 Open MPI 在全线程支持级别初始化时不会使用 native InfiniBand 通信。

    英特尔 MPI 有两种不同的风格——一种支持全多线程,一种不支持。通过传递 -mt_mpi 启用多线程支持MPI 编译器包装器的选项,它启用与 MT 版本的链接。如果启用了 OpenMP 支持或自动并行器,则也隐含此选项。我不知道启用全线程支持时 IMPI 中的 InfiniBand 驱动程序如何工作。

    MPICH(2) 不支持 InfiniBand,因此它是线程安全的并且可能最新版本提供 MPI_THREAD_MULTIPLE支持开箱即用。

    MVAPICH 是构建英特尔 MPI 的基础,它支持 InfiniBand。我不知道它在带有 InfiniBand 的机器上使用时在全线程支持级别上的表现如何。

    关于多线程 InfiniBand 支持的说明很重要,因为现在很多计算集群都使用 InfiniBand 结构。禁用 IB 组件(Open MPI 中的 openib BTL)后,大多数 MPI 实现会切换到另一种协议(protocol),例如 TCP/IP(Open MPI 中的 tcp BTL),这会导致通信速度更慢且延迟更高。

    关于c++ - 使用使用 std::async 创建的线程发送 MPI 的线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14836560/

    相关文章:

    c++ - 确定 mandelbrot 缩放的坐标

    c++ - C++ 是否弃用了 Linux API 的某些部分?

    c++ - strftime 的类似 snprintf 的合理替代品?

    c++ - 整数模板参数和子函数调用

    c++ - 将 std::array 传递给函数:默认值

    c++ - 从 'A&'类型的右值无效初始化 'A'类型的非const引用

    c++ - SQLite C/C++ 接口(interface) : memory increasing while insert data into table

    c++ - 在 Windows 中使用事件进行调试

    Java线程notify()方法

    java - 防止 4 路路口的汽车在 Java 中崩溃