Как полностью выйти из программы с резьбой C++?

Я создаю несколько потоков в моей программе. При нажатии Ctrl-C вызывается обработчик сигналов. Внутри обработчика сигнала я положил exit(0). Дело в том, что иногда программа прерывается безопасно, но в других случаях я получаю сообщение об ошибке времени выполнения

abort() has been called

Итак, каково было бы решение избежать ошибки?

Ответ 1

Обычный способ - установить атомный флаг (например, std::atomic<bool>), который проверяется всеми потоками (включая основной поток). Если установлено, то выход подчиненных потоков и основной поток начинают join к подпотокам. Затем вы можете выйти чисто.

Если вы используете std::thread для потоков, это возможная причина для аварий, которые у вас есть. Вы должны join к потоку, прежде чем объект std::thread будет разрушен.

Ответ 2

Другие упомянули, что обработчик сигнала задал std::atomic<bool> и все остальные потоки периодически проверяют это значение, чтобы знать, когда его нужно завершить.

Этот подход работает хорошо, пока все ваши другие потоки периодически пробуждаются, на разумной частоте.

Это не совсем удовлетворительно, если один или несколько ваших потоков являются чисто управляемыми событиями, однако - в программе, управляемой событиями, потоки должны только проснуться, когда есть некоторая работа для них, что означает, что они могут хорошо спать в течение нескольких дней или недель. Если они вынуждены просыпаться каждые (столько) миллисекунд просто для опроса атомно-булевского флага, это делает в противном случае чрезвычайно эффективную для ЦП программу с меньшим КПД, так как теперь каждый поток просыпается через короткие регулярные промежутки времени, 24/7/365. Это может быть особенно проблематичным, если вы пытаетесь сэкономить время автономной работы, так как это может помешать процессору перейти в энергосберегающий режим.

Альтернативный подход, который позволяет избежать опроса, будет следующим:

  1. При запуске ваш основной поток создает fd-pipe или пару сокетов (путем вызова pipe() или socketpair())
  2. Ваш основной поток (или, возможно, какой-то другой ответственный поток) включает приемный разъем в его готовом к чтению select() fd_set (или принять аналогичное действие для poll() или любой функции ожидания для ввода-вывода, в которой задействованы потоки)
  3. Когда обработчик сигнала выполняется, попросите его записать байта (любой байт, не имеет значения, что) в send-socket.
  4. Это приведет к немедленному возврату вызова основного потока select(), с FD_ISSET(receivingSocket) указывающим true из-за принятого байта
  5. В этот момент ваш основной поток знает, что пришло время для выхода процесса, поэтому он может начать направлять все свои дочерние потоки, чтобы начать закрытие (с помощью любого механизма, удобного, атомного булеана или труб или чего-то еще)
  6. После того, как все дочерние потоки начнут закрываться, основной поток должен затем вызвать join() для каждого дочернего потока, чтобы можно было гарантировать, что все дочерние потоки фактически исчезли до возвращения main(). (Это необходимо, потому что в противном случае существует риск состояния гонки - например, код очистки post-main() может иногда освобождать ресурс, пока все еще исполняемый дочерний поток все еще использует его, что приводит к сбою)

Ответ 3

Первое, что вы должны принять, - это то, что потоки сложны.

"Программа с использованием потоковой передачи" примерно такая же, как "программа, использующая память", и ваш вопрос похож на "как я не повреждаю память в программе, использующей память?"

Способ обработки проблемы с потоками заключается в том, чтобы ограничить использование потоков и поведение потоков.

Если ваша система потоковой передачи представляет собой кучу небольших операций, составленных в сети потоков данных, с неявной гарантией того, что если операция слишком велика, она разбивается на более мелкие операции и/или выполняет контрольные точки с системой, а затем выключение выглядит очень по-разному чем если у вас есть поток, который загружает внешнюю DLL, которая затем запускает ее где-то от 1 секунды до 10 часов до бесконечной длины.

Как и большинство вещей в C++, решение вашей проблемы будет связано с владением, контролем и (в крайнем случае) хаками.

Как и данные в C++, каждая нить должна принадлежать. Владелец потока должен иметь значительный контроль над этим потоком и иметь возможность сказать, что приложение закрывается. Механизм закрытия должен быть надежным и проверенным, и идеально связан с другими механизмами (например, с ранней остановкой спекулятивных задач).

Тот факт, что вы вызываете exit (0), является плохим знаком. Это означает, что ваш основной поток выполнения не имеет чистого пути завершения. Начните там; обработчик прерывания должен сигнализировать основному потоку, который должен начинаться с остановки, а затем ваш основной поток должен прекратить изящество. Все кадры стека должны разматываться, данные должны быть очищены и т.д.

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

Любой, кто говорит вам это так же просто, как переменная condition/atomic boolean, и опрос продает вам купюру. Это будет работать только в простых случаях, если вам повезет, и определить, будет ли он работать надежно, будет довольно сложно.

Ответ 4

Дополнительная информация для некоторых ответов программиста-чувака и связанные с обсуждением в разделе комментариев, вам нужно сделать флаг, который контролирует завершение ваших потоков как atomic тип.

Рассмотрим следующий случай:

bool done = false;
void pending_thread()
{
    while(!done)
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

Здесь рабочий поток также может быть вашим main потоком, и done завершение флага вашего потока, но ожидающий поток должен сделать что-то с заданными данными с помощью рабочего потока, прежде чем выйти.

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

Теперь исправленная версия с использованием std::automic:

std::atomic<bool> done(false);
void pending_thread()
{
    while(!done.load())
    {
        std::this_thread::sleep(std::milliseconds(1));
    }
    // do something that depends on working thread results
}

void worker_thread()
{
    //do something for pending thread
    done = true;
}

Вы можете выйти из потока, не заботясь о состоянии гонки или UB.