Дано: консольная программа, портируемая под винды и линукс.
Если нажать Ctrl+C или Ctrl+Break, или нажать на крестик у консоли, она крешится, виндоуз предлагает искать ошибку в интернете и всё такое.
Чтобы такого не было, добавил обработчик сигналов SIGINT, SIGTERM, SIGBREAK, — он пишет сообщение на экран и в журнал и вызывает exit(1)
Добавляю #pragma omp parallel for...
Программа в некоторых случаях завершается по exit(1), как и ожидалось, а в некоторых — рабочие потоки omp продолжают работать! Естественно, очень быстро они приходят к расстрелу памяти и дохнут по защите памяти.
Заменил exit на abort() или terminate() — та же фигня, некоторые потки продолжают работать после сигнала, только теперь виндоуз гарантированно предлагает искать ошибку.
Вопрос: как грамотно и портабельно убивать программу с omp, — при том, что нет задачи аккуратно джойнить потоки. Просто чтобы она молча останавливалась.
Второй вопрос: как она вообще может работать после exit() ?!
Программа — числодробилка, она загружает здоровенные бинарные файлы (под сотню мегабайт), долго их обрабатывает и пишет результат в текстовый файл.
Т.е. каждый поток проходит по такому циклу:
— 100% загрузка кернелмода (выделяет память; читает файл)
— 100% загрузка юзермода (считает)
— выдох — синхронизируется для записи в текстовый файл
Попробовал сделать дистиллированный пример такого вида — проблема не воспроизводится.
#include <csignal>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <cstdio>
#include <omp.h>
#include <chrono>
#include <thread>
using namespace std;
void halt_zuruck(int s)
{
cerr << "halted with code " << s << " in thread " << omp_get_thread_num() << endl;
exit(0);
}
char buf[1000000];
int main()
{
signal(SIGINT, halt_zuruck);
signal(SIGTERM, halt_zuruck);
signal(SIGBREAK, halt_zuruck); // это чисто виндовское - Ctrl+Break
cout << "go...";
#pragma omp parallel
{
int n = omp_get_thread_num();
int x = n+1;
std::chrono::milliseconds dura( n==0 ? 5000 : 500 );
while(true)
{
std::this_thread::sleep_for( dura );
for(int i=0; i<10000*(n+1); ++i)
{
FILE* f = fopen("x.cpp", "r"); // чтобы создать нагрузку на кернелмод
fread(buf, 1, sizeof(buf), f);
fclose(f);
}
for(int i=0; i<10000000; ++i)
x = x%2 ? x*3+1 : x/2; // чтобы создать нагрузку на юзермод
cout << n << " ";
}
}
}
Здравствуйте, Кодт, Вы писали:
К>Вопрос: как грамотно и портабельно убивать программу с omp, — при том, что нет задачи аккуратно джойнить потоки.
Как это нет?
Должна быть команда либо ожидания либо останова всех омп потоков.
К>Второй вопрос: как она вообще может работать после exit() ?!
А как по-твоему обычные потоки работают? Также и работают, программист не ставить ожидания закрытия потоков при выходе из первого потока, память под данными потоков овобждается и тут бац — акссес виолйшн или ещё что — поток то не остановлен был.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
К>Вопрос: как грамотно и портабельно убивать программу с omp, — при том, что нет задачи аккуратно джойнить потоки. Просто чтобы она молча останавливалась. К>Второй вопрос: как она вообще может работать после exit() ?!
Это уже не грамотно, поскольку выход по exit() перед непосредственно самокилянием процесса производит разрушение статических объектов и всяких структур рантайма, которые внезапно могут заюзаться другими потоками до того как exit() дойдет до самоубиения процесса. А не грамотно самый безболезненный способ самоубиться под виндой — TerminateProcess(GetCurrentProcess(), 0);
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Кодт, Вы писали:
К>Дано: консольная программа, портируемая под винды и линукс.
К>...
К сожалению, вариантов особо-то и нету: или добавлять флаг, проверяемый в цикле, или ExitProcess в винде — не знаю что там в никсах.
Опенмп вообще не затачивался на взаимодействие с рабочими потоками
MW>К сожалению, вариантов особо-то и нету: или добавлять флаг, проверяемый в цикле, или ExitProcess в винде — не знаю что там в никсах. MW>Опенмп вообще не затачивался на взаимодействие с рабочими потоками
ExitProcess может не помочь — он dll thread detach'и и FLS колбяки дергает. Так что только хардкор Terminate
Как много веселых ребят, и все делают велосипед...
Здравствуйте, smeeld, Вы писали:
S>А если завершать каждый поток искуственно?
Много мороки, хотя это и более правильно.
Числодробилка на то и числодробилка, что может очень глубоко задумываться.
Хотелось что-то такое кавалерийское, раз — и вдребезги пополам.
Здравствуйте, ononim, Вы писали:
O>Это уже не грамотно, поскольку выход по exit() перед непосредственно самокилянием процесса производит разрушение статических объектов и всяких структур рантайма, которые внезапно могут заюзаться другими потоками до того как exit() дойдет до самоубиения процесса. А не грамотно самый безболезненный способ самоубиться под виндой — TerminateProcess(GetCurrentProcess(), 0);
Ну вот, казалось бы, terminate() должен делать то же самое, только портабельно.
Ан нет, — под виндой он
— не сразу убивает рабочие потоки
— вызывает какие-то панические реакции — системный месседжбокс про проблемы и интернет.
exit() — если бы не тупил на статических объектах — подошёл бы идеально.
Здравствуйте, Кодт, Вы писали:
К>Дано: консольная программа, портируемая под винды и линукс.
Так это, а кто мешает проэнумировать все омп потоки вручную в процедуре для ctrl-c, засуспендить и терминировать их, а потом ждать пока все не омп потоки не остановятся? Если какие-то не омп потоки ждут омп потоки, то терминация омп потоков должна затриггерить ожидание и не омп потоки долны закрыться (в идеале конечно).
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
O>ExitProcess может не помочь — он dll thread detach'и и FLS колбяки дергает. Так что только хардкор Terminate
Можно отстреливать по частям: перечислить созданные процессом потоки, грохнуть их TerminateThread, потом аккуратно завершить процесс с хорошим exit code. Однако TerminateThread (и TerminateProcess(self) тоже) может зависнуть, если в момент терминирования он находится внутри какой-нибудь критической секции, которая не освобождается, но затем требуется в другом потоке.
Простейший пример: два потока пишут в консоль printf/cout, если один из них грохнуть в момент, когда он пишет — другой поток при попытке что-то вывести на консоль тупо зависнет. И уж обязательно зависнет, если попытаться вызвать exit (деаллокация консоли виснет).
С OpenMP я последний раз работал лет 10 назад, и уже в то время там использовались примитивы синхронизации.
SD>Можно отстреливать по частям: перечислить созданные процессом потоки, грохнуть их TerminateThread, потом аккуратно завершить процесс с хорошим exit code.
Вот в момент 'аккуратного завершения' все и повиснет
SD>Однако TerminateThread (и TerminateProcess(self) тоже) может зависнуть, если в момент терминирования он находится внутри какой-нибудь критической секции, которая не освобождается, но затем требуется в другом потоке.
TerminateThread (и TerminateProcess( сами по себе не повиснут, ибо они — прямые вызовы сервисов ядра. Юзермодного кода в них минимум — собственно сам вызов сервиса ядра. Никаких критических секций там и в помине нету.
Как много веселых ребят, и все делают велосипед...
насколько мне известно, в С++ нельзя звать 'exit()' и подобные, ибо констекст процесса будет подпорчен еще до того, как вся необходимая С++-лабуда почистится.
вариантов два:
1. в обработчике сигнала устанавливай флаг и в рабочих потоках проверяй его.
2. в обработчике сигнала, шли своему процессу SIGKILL.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Здравствуйте, niXman, Вы писали:
X>насколько мне известно, в С++ нельзя звать 'exit()' и подобные, ибо констекст процесса будет подпорчен еще до того, как вся необходимая С++-лабуда почистится. вот что говорят:
Note that objects with automatic storage are not destroyed by calling exit (C++).
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
К>Ну вот, казалось бы, terminate() должен делать то же самое, только портабельно. К>Ан нет, — под виндой он К>- не сразу убивает рабочие потоки К>- вызывает какие-то панические реакции — системный месседжбокс про проблемы и интернет. К>exit() — если бы не тупил на статических объектах — подошёл бы идеально.
В жизни каждого С программиста наступает момент, когда приходится писать #ifdef _WIN32/__APPLE__/whatever
Как много веселых ребят, и все делают велосипед...