Это немного поздно, но, надеюсь, этот пример кода поможет другим в подобной ситуации!
Как уже упоминалось в osgx, OpenMP ничего не говорит о проблеме сигналов, но так как OpenMP часто реализуетсяс pthreads в системах POSIX мы можем использовать подход с использованием сигнала pthread.
Для тяжелых вычислений, использующих OpenMP, вполне вероятно, что есть только несколько мест, где вычисления могут быть безопасно остановлены.Следовательно, для случая, когда вы хотите получить преждевременные результаты, мы можем использовать синхронную обработку сигналов, чтобы безопасно сделать это.Дополнительным преимуществом является то, что это позволяет нам принимать сигнал от определенного потока OpenMP (в приведенном ниже примере кода мы выбираем главный поток).При обнаружении сигнала мы просто устанавливаем флаг, указывающий, что вычисление должно быть остановлено.Затем каждый поток должен убедиться, что периодически проверяет этот флаг, когда это удобно, и затем оборачивает свою долю рабочей нагрузки.
Используя этот синхронный подход, мы позволяем вычислениям завершаться изящно и с минимальными изменениями в алгоритме.,С другой стороны, подход к обработчику сигналов по желанию может быть неуместным, поскольку, вероятно, будет трудно сопоставить текущие рабочие состояния каждого потока в согласованный результат.Один из недостатков синхронного подхода состоит в том, что для остановки может потребоваться заметное время.
Устройство проверки сигнала состоит из трех частей:
- Блокировка соответствующегосигналы.Это должно быть сделано за пределами области
omp parallel
, чтобы каждый поток OpenMP (pthread) унаследовал такое же поведение блокировки. - Опрос требуемых сигналов от основного потока.Для этого можно использовать
sigtimedwait
, но некоторые системы (например, MacOS) не поддерживают это.Точнее, мы можем использовать sigpending
для опроса любых заблокированных сигналов, а затем дважды проверить, что заблокированные сигналы - это то, что мы ожидаем, прежде чем принимать их синхронно, используя sigwait
(который должен тут же вернуться, если только какая-то другая частьПрограмма создает условия гонки).Наконец, мы устанавливаем соответствующий флаг. - Мы должны удалить нашу маску сигналов в конце (возможно, с одной последней проверкой сигналов).
Существуют некоторые важные соображения по поводу производительности и предостережения:
- Если предположить, что каждая итерация внутреннего цикла мала, выполнение системных вызовов для проверки сигнала является дорогостоящим.В примере кода мы проверяем сигналы только каждые 10 миллионов (на поток) итераций, что соответствует, возможно, паре секунд времени стены.
omp for
петли не могут быть разбиты из 1 , и поэтому вы должны либо вращаться до конца итераций, либо переписывать цикл, используя более простые примитивы OpenMP.Регулярные циклы (такие как внутренние циклы внешнего параллельного цикла) могут быть просто разбиты. - Если только главный поток может проверять сигналы, это может создать проблему в программах, где основной поток завершается.задолго до других тем.В этом случае эти другие потоки будут бесперебойными.Чтобы решить эту проблему, вы можете «передать эстафету» проверке сигналов, когда каждый поток завершает свою рабочую нагрузку, или главный поток может быть вынужден продолжать работать и опрашивать, пока все другие потоки не завершат 2 .
- На некоторых архитектурах, таких как HPC NUMA, время проверки «глобального» сигнального флага может быть довольно дорогим, поэтому будьте внимательны, когда решаете, когда и где проверять или манипулировать флагом.Например, для секции спинового цикла можно пожелать локально кэшировать флаг, когда он становится истинным.
Вот пример кода:
#include <signal.h>
void calculate() {
_Bool signalled = false;
int sigcaught;
size_t steps_tot = 0;
// block signals of interest (SIGINT and SIGTERM here)
sigset_t oldmask, newmask, sigpend;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
sigaddset(&newmask, SIGTERM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
#pragma omp parallel
{
int rank = omp_get_thread_num();
size_t steps = 0;
// keep improving result forever, unless signalled
while (!signalled) {
#pragma omp for
for (size_t i = 0; i < 10000; i++) {
// we can't break from an omp for loop...
// instead, spin away the rest of the iterations
if (signalled) continue;
for (size_t j = 0; j < 1000000; j++, steps++) {
// ***
// heavy computation...
// ***
// check for signal every 10 million steps
if (steps % 10000000 == 0) {
// master thread; poll for signal
if (rank == 0) {
sigpending(&sigpend);
if (sigismember(&sigpend, SIGINT) || sigismember(&sigpend, SIGTERM)) {
if (sigwait(&newmask, &sigcaught) == 0) {
printf("Interrupted by %d...\n", sigcaught);
signalled = true;
}
}
}
// all threads; stop computing
if (signalled) break;
}
}
}
}
#pragma omp atomic
steps_tot += steps;
}
printf("The result is ... after %zu steps\n", steps_tot);
// optional cleanup
sigprocmask(SIG_SETMASK, &oldmask, NULL);
}
Если используется C ++,Вам может пригодиться следующий класс ...
#include <signal.h>
#include <vector>
class Unterminable {
sigset_t oldmask, newmask;
std::vector<int> signals;
public:
Unterminable(std::vector<int> signals) : signals(signals) {
sigemptyset(&newmask);
for (int signal : signals)
sigaddset(&newmask, signal);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
}
Unterminable() : Unterminable({SIGINT, SIGTERM}) {}
// this can be made more efficient by using sigandset,
// but sigandset is not particularly portable
int poll() {
sigset_t sigpend;
sigpending(&sigpend);
for (int signal : signals) {
if (sigismember(&sigpend, signal)) {
int sigret;
if (sigwait(&newmask, &sigret) == 0)
return sigret;
break;
}
}
return -1;
}
~Unterminable() {
sigprocmask(SIG_SETMASK, &oldmask, NULL);
}
};
Блокирующую часть calculate()
можно заменить на Unterminable unterm();
, а часть проверки сигнала - if ((sigcaught = unterm.poll()) > 0) {...}
.Разблокировка сигналов выполняется автоматически, когда unterm
выходит из области видимости.
1 Это не совсем так.OpenMP поддерживает ограниченную поддержку для выполнения «параллельного разрыва» в виде точек отмены .Если вы решите использовать точки отмены в ваших параллельных циклах, убедитесь, что вы точно знаете, где находятся неявные точки отмены, чтобы гарантировать, что ваши расчетные данные будут согласованы при отмене.
2 Лично я веду подсчет того, сколько потоков завершило цикл for, и, если главный поток завершает цикл без перехвата сигнала, он продолжает опрашивать сигналы до тех пор, пока он не перехватит сигнал или все потоки не завершатся.петля.Для этого обязательно отметьте цикл for nowait
.