使用std :: async创建的线程发送MPI的线程安全性

时间:2013-02-12 16:01:57

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

根据this websiteMPI::COMM_WORLD.Send(...)的用法是线程安全的。但是在我的应用程序中,我经常(并非总是)遇到死锁或出现分段错误。用MPI::COMM_WORLDmutex.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。 fooconst std::shared_ptr<Commmunicator> &为参数。

我的问题:MPI承诺的线程安全性是否与std::async创建的线程不兼容?

2 个答案:

答案 0 :(得分:27)

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 ++绑定的等效代码:

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&lt; MPI_THREAD_FUNNELED&lt; MPI_THREAD_SERIALIZED&lt; 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不会使用本机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实现切换到另一个协议,例如TCP / IP(Open MPI中的tcp BTL),这导致更慢和更潜在通信。

答案 1 :(得分:1)

有四个级别的MPI线程安全性,并非所有实现都支持它们:MPI_THREAD_SINGLE,MPI_THREAD_FUNNELED,MPI_THREAD_SERIALIZED和MPI_THREAD_MULTIPLE。最后一个允许进程有多个线程可以同时调用MPI函数,可能是您感兴趣的。所以,首先,您需要确保您的实现支持MPI_THREAD_SERIALIZED。

必须通过调用MPI_Init_thread来指定所需的线程安全级别。调用MPI_Init_thread之后,您应该可以在自己创建的boost(POSIX)线程中安全地调用MPI函数。