2013-09-12 2 views
2

Этот вопрос основан на следующий вопрос: Handle CTRL+C on Win32Cleanup код в Win32 консольной программы

Я работаю на многопоточном сервере, работающий на Linux и Windows. Я не могу использовать boost или другие фреймворки, только std C++.

У меня проблема с кодом очистки на стороне win32. Линукс работает нормально: когда я хочу завершить работу сервера, я посылаю SIGINTCTRL+C), обработчик сигнала задает глобальную переменную, а основной pthread выполняет инструкции очистки (объединение других pthreads, освобождение памяти кучи и т. Д.), ,

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

#include <iostream> 
#include <windows.h> 

bool running; 

BOOL WINAPI consoleHandler(DWORD signal) { 

    if (signal == CTRL_C_EVENT) { 
     running = false; 
     std::cout << "[CTRL+C]\n"; 
     return TRUE; 
    } 

    return FALSE; 
} 

int main(int argc, char **argv) { 

    running = true; 

    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) { 

      std::cerr << "Error: " << GetLastError() << '\n'; 
      return -1; 
    } 

    std::cout << "Main thread working hard...\n"; 
    while (running) { ; } 

    for (int i = 0; i < 20; i++) 
     std::cout << "This is the " << i << "th fake cleanup instruction\n"; 

    return 0; 
} 

Выход следующий:

$ test.exe 
Main thread working hard... 
[CTRL+C] 
This is the 0th fake cleanup instruction 
This is the 1th fake cleanup instruction 

Таким образом, основная нить погибает быстро, только после двух инструкций. В предыдущем вопросе one of the suggestion было переместить очищающий код в обработчике, но на самом деле не помогает:

Предположим, что функция обработчика выглядит следующим образом:

BOOL WINAPI consoleHandler(DWORD signal) { 

    if (signal == CTRL_C_EVENT) { 
     running = false; 
     std::cout << "[CTRL+C]\n"; 

     for (int i = 0; i < 20; i++)  
      std::cout << "This is the " << i << "th fake cleanup instruction\n"; 

     return TRUE; 
    } 

    return FALSE; 
} 

Теперь поведение еще хуже! Выход:

$ test.exe 
Main thread working hard... 
[CTRL+C] 
This is the 

По MSDN, кажется, что этот процесс всегда убит:

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

  • Вызовите функцию ExitProcess, чтобы завершить процесс.
  • Return FALSE. Если ни одна из зарегистрированных функций обработчика не возвращает TRUE, обработчик по умолчанию завершает процесс.
  • Вернуться ИСТИНА. В этом случае никаких других функций обработки не вызываются, и система прекращает

процесса.

Я пропустил что-то очевидное? Каким образом можно завершить процесс консоли win32 и выполняет код очистки?

+2

Уверены ли вы, что проблемы, которые вы видите, связаны с отсутствием очистки 'cout', на основе того факта, что вы возвращаете« истину »из очистки, поэтому код завершается без вызова флеша, который вы обычно смотрите в 'exit'? –

+0

Просто заменил '' \ n'' на 'std :: endl', результат тот же. – eang

+0

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

ответ

3

Это один из способов сделать это, хотя я бы посоветовал вам использовать событие HANDLE и WaitForSingleObject, так как оно будет иметь тенденцию быть значительно более «уступчивым». Я оставил высокоскоростную спин-петлю в этом только для того, чтобы вы привязали одно из ваших ядер, все еще видя, что обработчик перехвачен.

Я взял на себя смелость изменить ваше рабочее состояние, чтобы его атомарно оценивали и устанавливали соответственно, поскольку я не хотел, чтобы оптимизатор выбрасывал eval в основном цикле.

#include <iostream> 
#include <cstdlib> 
#include <windows.h> 

// using an event for monitoring 
LONG running = 1; 

BOOL WINAPI consoleHandler(DWORD signal) 
{ 
    if (signal == CTRL_C_EVENT) 
    { 
     std::out << "Received Ctrl-C; shutting down..." << std::endl; 
     InterlockedExchange(&running, 0); 
     return TRUE; 
    } 
    return FALSE; 
} 

int main(int argc, char **argv) 
{ 
    if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) 
    { 
     std::cerr << "Error: " << GetLastError() << '\n'; 
     return EXIT_FAILURE; 
    } 

    std::cout << "Main thread working hard...\n"; 
    while (InterlockedCompareExchange(&running, 0, 0) == 1); 

    std::cout << "Graceful shutdown received. Shutting down now." << std::endl; 

    return 0; 
} 

Выход (примечание: я нажал Ctrl-C, в случае, если это не было очевидно)

Main thread working hard... 
Received Ctrl-C; shutting down... 
Graceful shutdown received. Shutting down now. 

Примечание: Я проверил это в отладке и выпуска как в 64 и 32 битных процессов, никаких проблем. И вы можете запустить его из отладчика VS. Просто выберите «Продолжить», когда вы сообщите, что можете продолжить, если у вас установлен обработчик, который вы делаете.

+0

Я не уверен, что вы имеете в виду с «привязкой одного из ваших ядер», но с этим кодом проблема одна и та же: основной поток убивается после второй итерации того, что я использовал в коде исходного вопроса. – eang

+0

@ital Я уверен, что знаю, почему это происходит. Установив 'running = 0' * before * в for-loop, вы инструктируете' main() 'закрывать. Поскольку обработчик работает в другом потоке (в документации говорится, что он это сделает), вы внедрили проблему параллелизма в течение всего времени в 'std :: cout'. Стандарт требует, чтобы он был доступен только в течение жизни 'main()'. Когда 'main()' возвращается в основной поток, RT-lib закрывает его. В вашем коде, следуя приведенной выше модели, что происходит, когда вы устанавливаете 'running' в ноль * после * вашего for-loop, а не * до *? – WhozCraig

+0

Просто проверено (для цикла в обработчике и 'running = false' после него), к сожалению, поведение не меняется: первые два' cout' в порядке, третий - в середине. – eang

2

В Windows вы можете использовать обработчик сигнала, а также:

static void shutdown(int signum) 
{ 
    printf("got signal #%d, terminating\n", signum); 
    // cleanup 
    _exit(1); 
} 

signal(SIGINT, shutdown); 
signal(SIGTERM, shutdown); 
signal(SIGSEGV, shutdown); 

Ctrl-C отображается в SIGINT так же, как на Linux.

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

+0

То же самое здесь, кажется, что основной поток убит быстро, также используя способ обработки «unix». – eang

Смежные вопросы