Как успешно завершить процесс с
Завершение работы процесса
Процесс выполняется до тех пор, пока не произойдет одно из нижеследующих событий:
- Какой-либо поток процесса вызывает функцию ExitProcess . Она завершает работу всех потоков процесса.
- Первичный поток процесса возвращает значение. Первичный поток может отменить завершение работы других потоков, при помощи явного вызова ExitThread перед возвратом своего значения. Один из оставшихся потоков может все еще вызвать ExitProcess , чтобы гарантировать, что все потоки завершили работу.
- Последний поток процесса завершил работу.
- Какой-либо поток вызывает функцию TerminateProcess с дескриптором процесса. Это завершает работу всех потоков процесса, без разрешения им, чтобы они очистили или сохранили данные.
- Для консольных процессов, заданная по умолчанию функция обработчика вызывает ExitProcess , когда консоль получает сигнал CTRL+BREAK или CTRL+C . Все консольные процессы, связанные с консолью получают эти сигналы. Отсоединенные процессы и процессы GUI — не затрагиваются сигналами CTRL+C , или CTRL+BREAK . Для получения дополнительной информации, см. статью SetConsoleCtrlHandler .
- Пользователь закрывает систему или заканчивает работу сессии. Используйте функцию SetProcessShutdownParameters , чтобы установить параметры выключения системы, типа того, когда процесс должен завершиться относительно других процессов в системе. Функция GetProcessShutdownParameters извлекает данные о текущем приоритете завершения процесса и других флажках завершения.
Когда процесс завершает работу, все потоки процесса немедленно завершают работу без шанса запустить дополнительный код. Это означает то, что процесс не выполняет код в блоках обработчика завершения работы. Для получения дополнительной информации, см. статью Структурная обработка особых ситуаций .
Функция GetExitCodeProcess возвращает значение состояние завершения процесса. В то время пока процесс выполняется, его состояние завершения является STILL_ACTIVE . Когда процесс заканчивает работу, его состояние завершения изменяется из STILL_ACTIVE в код выхода процесса. Код выхода является или значением, определяемым при вызове ExitProcess или TerminateProcess , или значением, возвращаемым функцией main или функцией WinMain процесса. Если процесс завершил работу в результате фатальной исключительной ситуации, код выхода является значением исключения, которое вызвало завершение. Кроме того, это значение используется как код выхода для всех потоков, которые выполнялись, когда произошла исключительная ситуация.
Когда процесс заканчивает работу, состояние объекта процесса становится сигнальным, освобождая любые потоки, которые ждали окончания работы процесса. За большей информацией о синхронизации, см. статью Выполнение синхронизации нескольких потоков .
Открытые дескрипторы файлов или других ресурсов закрываются автоматически, когда процесс завершает работу. Однако сами объекты существуют до тех пор, пока все их открытые дескрипторы не закроются. Это означает то, что объект остается допустимым после того, как процесс закрывается, если другой процесс имеет его дескриптор.
Если процесс завершил работу при помощи ExitProcess , система вызывает функцию точки входа каждой связанной динамически подключаемой библиотеки ( DLL ) со значением, указывающим, что процесс отсоединяется от DLL . Динамически связываемые библиотеки ( DLL ) не уведомляются, когда процесс завершает работу при помощи TerminateProcess . Для получения дополнительной информации о DLL , см. в главе Динамически-подключаемые библиотеки .
Выполнение функций ExitProcess , ExitThread , CreateThread , CreateRemoteThread и CreateProcess переводится в последовательный режим в пределах адресного пространства. Применяются нижеследующие ограничения:
- В ходе запуска процесса и DLL процедуры инициализации, могут быть созданы новые потоки, но они не начинают исполнение кода до тех пор, пока не закончена инициализация DLL для процесса.
- Только один поток одновременно может в DLL инициализировать или отключить процедуру.
- Функция ExitProcess не возвращает значения до тех пор, пока нет никаких потоков в их DLL , которые инициализируют или отключают процедуры.
Внимание! Функция TerminateProcess должна использоваться только в экстремальных обстоятельствах, так как она не дает возможности потокам очищать или сохранять данные и не уведомляет связанные с процессом DLL. Если Вам нужно иметь один процесс, который завершает другой процесс, то нижеследующие шаги обеспечивают лучшее решение этой проблемы:
- Необходимо, чтобы оба процесса вызвали функцию RegisterWindowMessage для создания нестандартного сообщения.
- Один процесс может завершить работу другого процесса, при помощи транслирования нестандартного сообщения, используя функцию BroadcastSystemMessage, как ниже указано:
- Процесс, получающий нестандартное сообщение вызывает ExitProcess , чтобы завершить свое выполнение.
Обратите внимание! , когда система завершает работу процесса, она не прерывает, какой-либо дочерний процесс, который этот процесс создал.
exit(1) в C++

В этой статье объясняется, как использовать функцию exit() в C++.
Please enable JavaScript
Используйте функцию exit для завершения программы C++ с кодом состояния
Запущенную программу обычно называют процессом в операционной системе, и у нее есть свой жизненный цикл, который представлен в виде различных состояний. Существуют давно работающие программы, такие как веб-серверы, но даже они должны быть остановлены при некоторых обстоятельствах или путем доставки сигнала пользователя. Завершение процесса можно вызвать с помощью функции exit() , которая является частью стандарта C и включена в C++. Он принимает один целочисленный аргумент, указывающий статус завершения программы, который может быть прочитан из родительского процесса, если дочерний процесс ожидает.
Обычное значение кода статуса успешного возврата — 0 , и все, кроме нуля, считается кодом ошибки, который может соответствовать заранее определенному сценарию. Обратите внимание, что есть два макро-выражения, называемые EXIT_SUCCESS и EXIT_FAILURE для обозначения соответствующих кодов выхода, переданных вызову exit . Как правило, программа имеет несколько файловых потоков и открытых временных файлов, которые закрываются или удаляются автоматически при вызове exit .
Используйте exit(1) для завершения программы C++ с кодом состояния сбоя
Функция exit со значением аргумента 1 используется для обозначения завершения с ошибкой. Некоторые функции, возвращающие значение кода состояния успешного вызова, могут быть объединены с условным оператором, который проверяет возвращаемое значение и выходит из программы в случае возникновения ошибки. Обратите внимание, что exit(1) аналогичен вызову exit(EXIT_FAILURE) .
Еще одна полезная особенность функции exit() — выполнение специально зарегистрированных функций до окончательного завершения программы. Эти функции могут быть определены как обычные функции, и для того, чтобы они вызывались по завершении, они должны быть зарегистрированы с помощью функции atexit . atexit является частью стандартной библиотеки и принимает указатель на функцию в качестве единственного аргумента. Обратите внимание, что несколько функций могут быть зарегистрированы с помощью нескольких вызовов atexit , и каждая из них вызывается в обратном порядке после выполнения exit .
7.11 – Остановки (преждевременный выход из программы)
Последняя категория инструкций управления порядком выполнения программы, которую мы рассмотрим, – это остановки. Остановка – это инструкция управления порядком выполнения программы, которая завершает программу. В C++ остановки реализованы как функции (а не как ключевые слова), поэтому наши инструкции остановки будут вызовами функций.
Давайте сделаем небольшой экскурс и вспомним, что происходит, когда программа завершается нормально. Когда функция main() завершается (либо достигая конца тела функции, либо с помощью инструкции return ), происходит ряд разных вещей.
Во-первых, поскольку мы выходим из функции, все локальные переменные и параметры функции уничтожаются (как обычно).
Затем вызывается специальная функция std::exit() со значением, возвращаемым из main() (код состояния), переданным в качестве аргумента. Так что же такое std::exit() ?
Функция std::exit()
std::exit() – это функция, которая вызывает нормальное завершение программы. Нормальное завершение означает, что программа завершилась ожидаемым образом. Обратите внимание, что термин «нормальное завершение» ничего не говорит о том, была ли работа программы успешной (для этого нужен код состояния). Например, предположим, что вы пишете программу, в которой ожидаете, что пользователь введет имя файла для обработки. Если пользователь ввел недопустимое имя файла, ваша программа, вероятно, вернет ненулевой код состояния, чтобы указать на состояние сбоя, но это всё равно будет нормальное завершение.
std::exit() выполняет ряд функций очистки. Сначала уничтожаются объекты со статической продолжительностью хранения. Затем выполняется дополнительная очистка файлов, если использовались какие-либо файлы. Наконец, управление возвращается обратно в ОС, а аргумент, переданный в std::exit() , используется в качестве кода состояния.
Явный вызов std::exit()
Хотя std::exit() при завершении функции main() вызывается неявно, она также может быть вызвана явно, чтобы остановить программу до того момента, как она завершится нормально. Когда std::exit() вызывается таким образом, вам нужно будет включить заголовок cstdlib .
Вот пример явного использования std::exit() :
Эта программа печатает:
Обратите внимание, что инструкции после вызова std::exit() никогда не выполняются, потому что программа уже завершена.
Хотя в приведенной выше программе мы вызываем std::exit() из функции main() , std::exit() можно вызвать из любой функции для завершения программы в необходимой точке.
Одно важное замечание о явном вызове std::exit() : std::exit() не очищает никакие локальные переменные (ни в текущей функции, ни в функциях вверх по стеку вызовов). По этой причине обычно лучше избегать вызова std::exit() .
Предупреждение
Функция std::exit() не очищает локальные переменные в текущей функции и в функциях выше по стеку вызовов.
std::atexit
Поскольку std::exit() завершает программу немедленно, вы можете перед завершением выполнить какую-либо очистку вручную (в приведенном выше примере мы вручную вызывали функцию cleanup() ). Поэтому программисту необходимо не забывать вручную вызывать функцию очистки перед каждым вызовом exit() .
Чтобы помочь в этом, C++ предлагает функцию std::atexit() , которая позволяет вам указать функцию, которая будет автоматически вызываться при завершении программы через std::exit() .
Эта программа имеет тот же вывод, что и предыдущий пример:
Так зачем вам это делать? Это позволяет вам указать функцию очистки в одном месте (возможно, в main ), а затем не беспокоиться о том, чтобы не забыть вызвать эту функцию явно перед вызовом std::exit() .
Несколько замечаний о std::atexit() и функции очистки: во-первых, поскольку std::exit() вызывается неявно при завершении main() , если программа завершается таким образом, это вызовет любые функции, зарегистрированные std::atexit() . Во-вторых, регистрируемая функция не должна принимать никаких параметров и должна не иметь возвращаемого значения. Наконец, с помощью std::atexit() вы можете зарегистрировать несколько функций очистки, если хотите, и они будут вызываться в порядке, обратном порядку регистрации (последняя зарегистрированная будет вызываться первой).
Для продвинутых читателей
В многопоточных программах вызов std::exit() может привести к сбою вашей программы (поскольку поток, вызывающий std::exit() , будет очищать статические объекты, к которым могут обращаться другие потоки). По этой причине в C++ появилась еще одна пара функций, которые работают аналогично std::exit() и std::atexit() , – std::quick_exit() и std::at_quick_exit() . std::quick_exit() завершает программу нормально, но не очищает статические объекты и может выполнять или не выполнять другие типы очистки. std::at_quick_exit() выполняет ту же роль, что и std::atexit() для программ, завершаемых с помощью std::quick_exit() .
std::abort и std::terminate
C++ также содержит две другие функции, связанные с остановкой.
Функция std::abort() вызывает аварийное завершение вашей программы. Аварийное завершение означает, что в программе произошла какая-то необычная ошибка времени выполнения, и программа не может продолжать работу. Например, к аварийному завершению попытка разделить на 0 приведет. std::abort() не выполняет никакой очистки.
Случаи, когда std::abort вызывается неявно, мы увидим в этой главе позже (7.17 – assert и static_assert ).
Функция std::terminate() обычно используется вместе с исключениями (мы рассмотрим исключения в следующей главе). Хотя std::terminate можно вызвать явно, она чаще вызывается неявно, когда исключение не обрабатывается (и в некоторых других случаях, связанных с исключениями). По умолчанию std::terminate() вызывает std::abort() .
Когда следует использовать остановки?
Короткий ответ: «почти никогда». В C++ уничтожение локальных объектов является важной частью C++ (особенно когда мы дойдем до классов), и ни одна из вышеупомянутых функций не очищает локальные переменные. Исключения – лучший и безопасный механизм обработки ошибок.
Лучшая практика
Используйте остановки только в том случае, если нет безопасного способа нормально вернуться из функции main . Если вы не отключили исключения, используйте их для безопасной обработки ошибок.
QProcess не убивает / не завершает процесс, если он определен в куче
Я хочу убить / завершить процесс, который я создал при выходе из приложения:
С версией 1: мое приложение запускается так, как я ожидаю: создать и уничтожить процесс успешно, но при выходе из приложения QCreator сообщает: «QProcess: уничтожено, пока процесс (« caffeinate ») все еще работает».
С версией 2: мое приложение может запустить процесс, но не может завершить или завершить процесс при выходе, и нет отчетов, как указано выше.
Я просто хочу спросить, почему при создании в куче, QProcess не может быть убит как стационарная версия? Спасибо!
(Я использовал struct Lambda, потому что я не могу использовать C ++ 11 лямбда в моем проекте)
Решение
ТЛ; др
В обоих случаях сигналы не доставляются; в первом случае деструктор убивает процесс, во втором он даже не имеет возможности запустить.
В общем, ваш код представляет собой хороший сборник практически всех бездушных QObject , QThread , сигналы и т.п .; читать Потоки и объекты QObject прежде чем делать что-либо с потоками, объектами QObject и сигналами в Qt. Это важная информация, без которой вы будете только делать беспорядок, как это. * Также этот Статья в вики дает хорошее изложение «правильного способа» использовать потоки с Qt.
Детальное объяснение
Давайте назовем основной поток нить А и тема началась QtConcurrent::run резьба B.
Случай 1
когда run запускается из второго потока, p создан, поэтому он имеет родство нитей с резьбой В. По этой причине все connect Вы выступаете на нем соединения в очереди (по умолчанию для connect является AutoConnection , который использует QueuedConnection если связанные объекты имеют различное сродство потока — и qApp создан в нить А).
Проблема в том, что соединения в очереди работают, только если в принимающем потоке работает цикл обработки событий (они реализованы как sendEvent , так что если в целевом потоке нет событий обработки цикла, они только накапливаются в очереди событий), тогда как здесь run возвращается сразу после запуска процесса.
Так, kill , terminate , close а также deleteLater никогда не называются. Заметить, что:
- призвание deleteLater в этом случае все равно было бы ошибкой, так как он будет пытаться сделать delete на static объект;
- ни kill ни terminate являются синхронными, поэтому, чтобы убедиться, что процесс мертв, прежде чем продолжить, вам нужно было бы также waitForFinished ;
- также, возможно, нить, которая была закручена QtConcurrent::run будет мертвым после run завершаясь 1 ; это определенно плохо, потому что вы будете иметь QObject s лежит вокруг с привязанностью нити к нити, которая мертва. Я не знаю, как изящно sendEvent обрабатывает эту ситуацию.
Во всяком случае, когда программа заканчивается, p деструктор вызывается автоматически как обычная часть завершения работы приложения C ++ 2 ; как задокументировано , деструктор QProcess завершает процесс, с которым он связан, если он все еще работает (но также записывает «страшное сообщение», которое вы видели).
Дело 2
Как и в случае 1, вы создаете QProcess с резьба B сродство; так что все, что мы сказали выше о событиях не доставляются & сотрудничество. все еще применяется.
Здесь есть три основных различия:
- вы устанавливаете родителя p в qApp , который живет в главном потоке; это явно запрещено, все родительско-дочерние отношения между объектами QObject должны существовать между объектами с одинаковой привязкой к потоку; вероятно, вы получаете какое-то предупреждение в консоли об этом факте ( setParent Явно проверяю, живут ли объекты в одном потоке QObject конструктор делает то же самое);
- в этом случае deleteLater могло бы быть уместным (если бы у вас был цикл обработки событий), так как вы выделяли new ;
- но самое главное, здесь p деструктор никогда не вызывается, так как он был наделен new и никто не звонит delete в теме; по этой причине запущенный процесс продолжает работать (также у вас небольшая утечка памяти).
Итак, что было бы правильным способом справиться с этим? Лично я бы вообще избежал потоков и сигналов. Запуск процесса уже асинхронный, так что вы могли бы просто сделать:
как всегда с потоками, очередями событий, сигналами и тому подобным: не усложняйте, чем нужно.
Сноски
На практике в этом случае вы, вероятно, не заметите, потому что QtConcurrent использует глобальный пул потоков, который убивает закрученные потоки только после 30 секунд простоя.
Общий совет: обычно вы не хотите, чтобы «сложные» объекты уничтожались таким образом, так как main уже завершено, поэтому (1) это усложняет отладку и (2) если у вас есть объекты Qt, которые зависят от QApplication еще жив (как правило, все в QtGui и QtWidgets) вы начнете получать странные сбои при завершении программы.