Подтвердить что ты не робот

Безопасность потоков MPI-передачи с использованием потоков, созданных с помощью std:: async

Согласно этот веб-сайт, использование MPI::COMM_WORLD.Send(...) является потокобезопасным. Однако в моем приложении я часто (не всегда) сталкиваюсь с тупиковыми ошибками или получаю ошибки сегментации. Включение каждого вызова методов MPI::COMM_WORLD с помощью mutex.lock() и mutex.unlock() последовательно удаляет блокировки, а также segfaults.

Вот как я создаю потоки:

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?

4b9b3361

Ответ 1

Безопасность потоков в MPI не работает из коробки. Во-первых, вы должны убедиться, что ваша реализация фактически поддерживает несколько потоков, вызывающих вызовы MPI сразу. С некоторыми реализациями MPI, например Open MPI, это требует, чтобы библиотека была настроена со специальными параметрами во время сборки. Затем вы должны указать MPI для инициализации на соответствующем уровне поддержки потоков. В настоящее время стандарт MPI определяет четыре уровня поддержки потоков:

  • MPI_THREAD_SINGLE - означает, что код пользователя является однопоточным. Это уровень по умолчанию, при котором 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 support\n");
    MPI_Abort(MPI_COMM_WORLD, 1);
}

Эквивалентный код с устаревшим (и удаленным из MPI-3) привязками С++:

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.

Для получения дополнительной информации см. §12.4 стандарта MPI, свободно доступный здесь.

С большинством реализаций MPI поддержка потоков на уровне MPI_THREAD_SINGLE фактически эквивалентна поддержке на уровне MPI_THREAD_SERIALIZED - именно то, что вы наблюдаете в своем случае.

Поскольку вы не указали, какую реализацию MPI вы используете, вот список удобных.

Я уже говорил, что Open MPI должен быть скомпилирован с соответствующими флагами, чтобы поддерживать MPI_THREAD_MULTIPLE. Но есть еще один улов - компонент InfiniBand не является потокобезопасным, и поэтому Open MPI не будет использовать собственную связь InfiniBand при инициализации на уровне поддержки потоков.

Intel MPI поставляется в двух разных вариантах: один с одним и один без поддержки полного многопоточности. Поддержка многопоточности разрешена путем передачи опции -mt_mpi в оболочку компилятора MPI, которая позволяет связывать с версией MT. Этот параметр также подразумевается, если включена поддержка OpenMP или включен автопараллелизатор. Я не знаю, как работает драйвер InfiniBand в IMPI, когда включена поддержка полного потока.

MPICH (2) не поддерживает InfiniBand, поэтому он является потокобезопасным, и, возможно, самые последние версии предоставляют поддержку MPI_THREAD_MULTIPLE из коробки.

MVAPICH является основой, на которой построен Intel MPI, и поддерживает InfiniBand. Я понятия не имею, как он ведет себя на полном уровне поддержки потоков при использовании на машине с InfiniBand.

Заметка о многопоточной поддержке InfiniBand важна, поскольку в настоящее время множество вычислительных кластеров используют ткани InfiniBand. Когда компонент IB (openib BTL в Open MPI) отключен, большинство реализаций MPI переключается на другой протокол, например TCP/IP (tcp BTL в Open MPI), что приводит к значительно более медленной и более скрытой связи.

Ответ 2

Существует четыре уровня безопасности потока MPI, но не все из них поддерживаются каждой реализацией: MPI_THREAD_SINGLE, MPI_THREAD_FUNNELED, MPI_THREAD_SERIALIZED и MPI_THREAD_MULTIPLE. Последний, который позволяет процессу иметь несколько потоков, которые могут одновременно вызывать функции MPI, вероятно, тот, который вам интересен. Поэтому, прежде всего, вам нужно убедиться, что ваша реализация поддерживает MPI_THREAD_SERIALIZED.

Требуемый уровень безопасности потока должен быть задан вызовом MPI_Init_thread. После того, как вы вызвали MPI_Init_thread, вы сможете безопасно вызывать функции MPI в потоках boost (POSIX), созданных самостоятельно.