Обработка сигналов в параллельной программе OpenMP - PullRequest
8 голосов
/ 15 ноября 2011

У меня есть программа, которая использует POSIX таймер (timer_create()).По сути, программа устанавливает таймер и начинает выполнять длительные (потенциально бесконечные) вычисления.Когда время таймера истекает и вызывается обработчик сигнала, обработчик печатает лучший результат, который был вычислен, и выходит из программы.

Я считаю, что нужно выполнять вычисления параллельно с использованием OpenMP, потому что это должно ускорить его.

В pthreads есть специальные функции, например, для установки масок сигналов для моих потоков или около того.Предоставляет ли OpenMP такой контроль, или я должен принять тот факт, что сигнал может быть доставлен в любой из потоков, которые создает OpenMP?

Кроме того, в случае, если я в настоящее время нахожусь в параллельном разделе моего кода и моеговызывается обработчик, может ли он по-прежнему безопасно уничтожать приложение (exit(0);) и выполнять такие действия, как блокировка блокировок OpenMP?

Ответы [ 2 ]

2 голосов
/ 01 мая 2019

Это немного поздно, но, надеюсь, этот пример кода поможет другим в подобной ситуации!


Как уже упоминалось в 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.

2 голосов
/ 16 ноября 2011

Стандарт OpenMP 3.1 ничего не говорит о сигналах.

Как я знаю, каждая популярная реализация OpenMP в Linux / UNIX основана на pthreads, поэтому поток OpenMP является потоком pthread. И общие правила pthreads и сигналов применяются.

Обеспечивает ли OpenMP такой контроль

Нет какого-либо конкретного контроля; но вы можете попытаться использовать контроль pthread. Единственная проблема состоит в том, чтобы узнать, сколько потоков OpenMP используется и где разместить управляющий оператор.

сигнал может быть доставлен в любой из потоков, создаваемых OpenMP?

По умолчанию да, он будет доставлен в любой поток.

мой обработчик называется,

Обычные правила для обработчика сигналов все еще применяются. Функции, разрешенные в обработчике сигналов, перечислены в http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html (в конце страницы)

И printf не разрешены (write есть). Вы можете использовать printf, если вы знаете, что в момент подачи сигнала printf не используется никаким потоком (например, у вас нет printf в параллельной области).

может ли оно безопасно завершить работу приложения (выход (0);)

Да, это возможно: abort() и _exit() разрешены из обработчика.

Linux / Unix прервет все потоки, если какой-либо поток exit или abort.

и есть ли такие вещи, как блокировка блокировок OpenMP?

Не следует, но если вы знаете, что эта блокировка не будет заблокирована во время запуска обработчика сигнала, вы можете попытаться сделать это.

!! UPDATE

Существует пример принятия сигнализации для OpenMP http://www.cs.colostate.edu/~cs675/OpenMPvsThreads.pdf («OpenMP против потоков в C / C ++»). Вкратце: установите флаг в обработчике и добавляйте проверки этого флага в каждом потоке на каждой итерации N-го цикла.

Адаптация механизма исключений на основе сигналов к параллельной области

Что-то, что происходит больше с C / C ++ приложения, которые с приложениями Fortran является то, что Программа использует сложный пользовательский интерфейс. Genehunter - простой пример, где пользователь может прервать вычисление одного семейного древа нажав Control-C, чтобы он мог перейти к Следующее генеалогическое древо в клинической базе данных о болезни. Преждевременное прекращение обрабатывается в серийная версия в виде исключения C ++ механизм, включающий обработчик сигнала, setjump, и longjump.OpenMP не разрешает неструктурированный контроль поток, чтобы пересечь параллельную границу конструкции. Мы изменена обработка исключений в OpenMP версия, изменив обработчик прерываний в механизм опроса. Нить, которая ловит сигнал control-C устанавливает общий флаг. Все темы отметьте флаг в начале цикла вызов подпрограммы has_hit_interrupt () и пропустите итерацию, если она установлена. Когда петля заканчивается, мастер проверяет флаг и может легко выполнить longjump, чтобы завершить исключительный выход (см. рисунок 1)

...