使用使用 std::async 创建的线程发送的 MPI 发送的线程安全性
thread safety of MPI send using threads created with std::async
根据这个网站,MPI::COMM_WORLD.Send(...)
的使用是线程安全的。但是,在我的应用程序中,我经常(并非总是(遇到死锁或出现分段错误。用mutex.lock()
和mutex.unlock()
将MPI::COMM_WORLD
方法的每个调用括起来,可以始终如一地消除死锁和段错误。
这是我创建线程的方式:
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承诺的线程安全是否与std::async
创建的线程不兼容?
MPI 中的线程安全不是开箱即用的。首先,必须确保您的实现实际上支持多个线程同时进行 MPI 调用。对于某些 MPI 实现,例如 Open MPI,这需要在构建时使用特殊选项配置库。然后,您必须告诉 MPI 在适当的线程支持级别进行初始化。目前,MPI 标准定义了四个级别的线程支持:
-
MPI_THREAD_SINGLE
- 表示用户代码是单线程的。如果使用 MPI,这是初始化 MPI 的默认级别MPI_Init()
; -
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 supportn");
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 supportn");
MPI::COMM_WORLD.Abort(1);
}
线程支持级别按如下顺序排列:MPI_THREAD_SINGLE
<MPI_THREAD_FUNNELED
><MPI_THREAD_SERIALIZED
>
MPI_Init(&argc, &argv)
相当于MPI_Init_thread(&argc, &argv, MPI_THREAD_SINGLE, &provided)
。实现不需要在请求的级别精确初始化 - 而是可以在任何其他级别(更高或更低(初始化,这在 provided
输出参数中返回。
有关更多信息 - 请参阅 MPI 标准的 §12.4,可在此处免费获得。
对于大多数 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组件(openib
开放MPI中的BTL(后,大多数MPI实现切换到另一种协议,例如TCP/IP(tcp
开放MPI中的BTL(,这会导致更慢和更潜在的通信。
MPI 线程安全有四个级别,并非所有级别都支持每个实现:MPI_THREAD_SINGLE、MPI_THREAD_FUNNELED、MPI_THREAD_SERIALIZED 和 MPI_THREAD_MULTIPLE。最后一个,它允许一个进程有多个线程,可以同时调用 MPI 函数,可能是你感兴趣的那个。因此,首先,您需要确保您的实现支持MPI_THREAD_SERIALIZED。
所需的线程安全级别必须通过调用 MPI_Init_thread
来指定。调用MPI_Init_thread
后,您应该能够在自己创建的提升 (POSIX( 线程中安全地调用 MPI 函数。
- 从不同线程使用int64的不同字节安全吗
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 在C++中使用cURL和多线程
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在cuda线程之间共享大量常量数据
- 如何将元素添加到数组的线程安全函数?
- 线程,如果else语句,都是错误的上下文切换后,会发生什么
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 异常属于C++中的线程还是进程
- 混合开放MP/MPI代码中的多线程发送/接收时出错
- 我有 12 个 CPU,1 个插槽,每个插槽 6 个内核,每个内核 2 个线程 - 这些信息如何对应于 MPI 和 Op
- MPI - 当数组初始化值必须为常量时,如何为工作线程创建部分数组
- 为什么我所有的 mpi 线程都是大师
- 线程与共享内存和MPI之间的主要区别
- 如何在MPI + openmp中启动多线程
- 序列化mpi线程
- 线程安全MPI通信
- 使用使用 std::async 创建的线程发送的 MPI 发送的线程安全性
- 将 MPI 与线程一起使用的正确方法是什么