Прекращение потока изящно, не используя TerminateThread () - PullRequest
9 голосов
/ 09 ноября 2009

Мое приложение создает поток, который все время работает в фоновом режиме. Я могу завершить поток только вручную, а не из функции обратного вызова потока. В настоящее время я использую TerminateThread(), чтобы убить этот поток, но иногда он зависает. Я знаю, что есть способ использовать события и WaitForSingleObject(), чтобы изящно завершить поток, но я не могу найти пример по этому поводу.

Пожалуйста, здесь нужен код.

Ответы [ 4 ]

19 голосов
/ 09 ноября 2009

TerminateThread - плохая идея, особенно если ваш поток использует объекты синхронизации, такие как мьютексы. Это может привести к освобождению памяти и дескрипторов, а также к тупикам, поэтому вы правы, что вам нужно сделать что-то еще.

Как правило, способ завершения потока - это возврат из функции, которая определяет поток. Основной поток сигнализирует рабочему потоку о выходе, используя объект события или даже простое логическое значение, если это проверяется достаточно часто. Если рабочий поток ожидает с WaitForSingleObject, вам может потребоваться изменить его на WaitForMultipleObjects, где один из объектов является событием. Основной поток вызовет SetEvent, а рабочий поток проснется и вернется.

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

Кроме того, в [сейчас очень старом] MSVC вам необходимо использовать _beginthreadex вместо CreateThread, чтобы избежать утечек памяти в CRT. См. MSKB # 104641 .

Обновление:

Одно из применений рабочего потока - это «таймер», чтобы выполнять некоторые операции через регулярные промежутки времени. Самое простое:

for (;;) {
    switch (WaitForSingleObject(kill_event, timeout)) {
        case WAIT_TIMEOUT: /*do timer action*/ break;
        default: return 0; /* exit the thread */
    }
}

Другое использование - делать что-то по требованию. В основном то же самое, но с таймаутом, установленным на INFINITE и выполняющим некоторые действия на WAIT_OBJECT_0 вместо WAIT_TIMEOUT. В этом случае вам потребуются два события: одно, чтобы поток проснулся и выполнил какое-то действие, другой - чтобы он проснулся и вышел:

HANDLE handles[2] = { action_handle, quit_handle };
for (;;) {
    switch (WaitForMultipleObject(handles, 2, FALSE, INFINITE)) {
        case WAIT_OBJECT_0 + 0: /* do action */ break;
        default:
        case WAIT_OBJECT_0 + 1: /* quit */ break;
    }
}

Обратите внимание, что в цикле важно сделать что-то разумное, если WFSO / WFMO вернет ошибку вместо одного из ожидаемых результатов. В обоих приведенных выше примерах мы просто относимся к ошибке, как если бы нам дали сигнал о выходе.

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

1 голос
/ 09 ноября 2009

Поскольку вы не знаете, что делает поток, нет способа безопасно завершить поток снаружи.

Почему вы думаете, что не можете прекратить это изнутри?

Вы можете создать событие до запуска потока и передать дескриптор этого события в поток. Вы вызываете SetEvent() в этом событии из основного потока, чтобы дать сигнал потоку остановиться, а затем WaitForSingleObject в дескрипторе потока, чтобы дождаться его завершения. В цикле потоков вы вызываете WaitForSingleObject() для события, указывая время ожидания 0 (ноль), так что вызов немедленно возвращается, даже если событие не установлено. Если этот вызов возвращает WAIT_TIMEOUT, событие не установлено, если оно возвращает WAIT_OBJECT_0, оно установлено. В последнем случае вы возвращаетесь из функции потока.

Я предполагаю, что ваш поток не просто записывает циклы процессора в бесконечном цикле, но делает некоторое ожидание, возможно, путем вызова Sleep(). Если это так, вы можете вместо этого сделать сон в WaitForSingleObject, передав ему тайм-аут.

0 голосов
/ 20 февраля 2015

Это пример кода для управления потоками способом fork-join. Он использует struct Thread в качестве дескриптора потока.

Давайте представим некоторую абстракцию структуры данных дескриптора потока:

    #include <Windows.h>

    struct Thread
    {
        volatile BOOL stop;

        HANDLE event;
        HANDLE thread;
    };

    typedef DWORD ( __stdcall *START_ROUTINE)(struct Thread* self, LPVOID lpThreadParameter);

    struct BootstrapArg
    {
        LPVOID arg;
        START_ROUTINE body;
        struct Thread* self;
    };

Функции для родительского потока:

  • StartThread () инициализирует эту структуру и запускает новый поток.
  • StopThread () инициирует завершение потока и ждет, пока поток будет фактически завершен.

    DWORD __stdcall ThreadBootstrap(LPVOID lpThreadParameter)
    {
        struct BootstrapArg ba = *(struct BootstrapArg*)lpThreadParameter;
    
        free(lpThreadParameter);
    
        return ba.body(ba.self, ba.arg);
    }
    
    VOID StartThread(struct Thread* CONST thread, START_ROUTINE body, LPVOID arg)
    {
        thread->event = CreateEvent(NULL, TRUE, FALSE, NULL);
        thread->stop = FALSE;
        thread->thread = NULL;
    
        if ((thread->event != NULL) && (thread->event != INVALID_HANDLE_VALUE))
        {
            struct BootstrapArg* ba = (struct BootstrapArg*)malloc(sizeof(struct BootstrapArg));
    
            ba->arg = arg;
            ba->body = body;
            ba->self = thread;
    
            thread->thread = CreateThread(NULL, 0, ThreadBootstrap, ba, 0, NULL);
            if ((thread->thread == NULL) || (thread->thread == INVALID_HANDLE_VALUE))
            {
                free(ba);
            }
        }
    }
    
    DWORD StopThread(struct Thread* CONST thread)
    {
        DWORD status = ERROR_INVALID_PARAMETER;
    
        thread->stop = TRUE;
    
        SetEvent(thread->event);
        WaitForSingleObject(thread->thread, INFINITE);
    
        GetExitCodeThread(thread->thread, &status);
        CloseHandle(thread->event);
        CloseHandle(thread->thread);
    
        thread->event = NULL;
        thread->thread = NULL;
    
        return status;
    }
    

Ожидается, что этот набор функций будет использоваться из потока, запущенного StartThread ():

  • IsThreadStopped () - Проверить запрос на завершение. Должен использоваться после ожидания для следующих функций, чтобы определить фактическую причину завершения состояния ожидания.
  • ThreadSleep () - Заменяет использование Sleep () для кода внутри потока.
  • ThreadWaitForSingleObject () - Заменяет использование WaitForSingleObject () для внутрипоточного кода.
  • ThreadWaitForMultipleObjects () - Заменяет использование WaitForMultipleObjects () для внутрипоточного кода.

Первая функция может использоваться для облегченных проверок запроса завершения во время длительной обработки задания. (Например, сжатие больших файлов). Остальные функции обрабатывают случай ожидания некоторых системных ресурсов, таких как события, семафоры и т. Д. (Например, рабочий поток ожидает новый запрос, поступающий из очереди запросов).

BOOL IsThreadStopped(struct Thread* CONST thread)
{
    return thread->stop;
}

VOID ThreadSleep(struct Thread* CONST thread, DWORD dwMilliseconds)
{
    WaitForSingleObject(thread->event, dwMilliseconds);
}

DWORD ThreadWaitForSingleObject(struct Thread* CONST thread, HANDLE hHandle, DWORD dwMilliseconds)
{
    HANDLE handles[2] = {hHandle, thread->event};

    return WaitForMultipleObjects(2, handles, FALSE, dwMilliseconds);
}

DWORD ThreadWaitForMultipleObjects(struct Thread* CONST thread, DWORD nCount, CONST HANDLE* lpHandles, DWORD dwMilliseconds)
{
    HANDLE* handles = (HANDLE*)malloc(sizeof(HANDLE) * (nCount + 1U));
    DWORD status;

    memcpy(handles, lpHandles, nCount * sizeof(HANDLE));

    handles[nCount] = thread->event;
    status = WaitForMultipleObjects(2, handles, FALSE, dwMilliseconds);

    free(handles);

    return status;
}
0 голосов
/ 09 ноября 2009

Что ты делаешь в фоновом потоке? Если вы зацикливаетесь на чем-то, вы можете завершить поток внутри себя, имея общий публичный статический объект (например, Boolean), для которого вы установили true из потока переднего плана, и что фоновый поток проверяет и завершает работу корректно при установке на true.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...