Прекращение потока изящно, не используя TerminateThread()

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

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

4 ответа

Решение

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

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

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

Кроме того, в [сейчас очень старый] MSVC, вам нужно использовать _beginthreadex вместо CreateThread во избежание утечек памяти в ЭЛТ. См. 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 и выйти, но я бы не рекомендовал такой подход.

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

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

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

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

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

Это пример кода для управления потоками в стиле 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;
}
Другие вопросы по тегам