Подтвердить что ты не робот

Пробуждать поток заблокирован при вызове accept()

Сокеты на Linux вопрос

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

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

Некоторые вещи, которые я пробовал:

  • pthread_kill для отправки сигнала. Чувствует, что это делает kludgy, плюс он не надежно разрешает потоку выполнять эту логику выключения. Также прекращает работу программы. Я бы хотел избежать сигналов, если это вообще возможно.

  • pthread_cancel

    . То же, что и выше. Это суровое убийство на нитке. Это, и поток может делать что-то еще.

  • Закрытие гнезда для прослушивания из основного потока, чтобы сделать accept() отменено. Это не работает надежно.

Некоторые ограничения:

Если решение подразумевает включение блокировки прослушивания, это нормально. Но я не хочу принимать решение, которое включает поток, просыпающийся через вызов select каждые несколько секунд, чтобы проверить условие выхода.

Условие потока для выхода не может быть привязано к завершаемому процессу.

По сути, логика, которую я собираюсь, выглядит так.

void* WorkerThread(void* args)
{
    DoSomeImportantInitialization();  // initialize listen socket and some thread specific stuff

    while (HasExitConditionBeenSet()==false)
    {
        listensize = sizeof(listenaddr);
        int sock = accept(listensocket, &listenaddr, &listensize);

        // check if exit condition has been set using thread safe semantics
        if (HasExitConditionBeenSet())
        {
            break;
        }

        if (sock < 0)
        {
            printf("accept returned %d (errno==%d)\n", sock, errno);
        }
        else
        {
            HandleNewNetworkCondition(sock, &listenaddr);
        }
    }

    DoSomeImportantCleanup(); // close listen socket, close connections, cleanup etc..
    return NULL;
}

void SignalHandler(int sig)
{
    printf("Caught CTRL-C\n");
}

void NotifyWorkerThreadToExit(pthread_t thread_handle)
{
    // signal thread to exit
}

int main()
{
    void* ptr_ret= NULL;
    pthread_t workerthread_handle = 0;

    pthread_create(&workerthread, NULL, WorkerThread, NULL);

    signal(SIGINT, SignalHandler);

    sleep((unsigned int)-1); // sleep until the user hits ctrl-c

    printf("Returned from sleep call...\n");

    SetThreadExitCondition(); // sets global variable with barrier that worker thread checks on

    // this is the function I'm stalled on writing
    NotifyWorkerThreadToExit(workerthread_handle);

    // wait for thread to exit cleanly
    pthread_join(workerthread_handle, &ptr_ret);

    DoProcessCleanupStuff();

}
4b9b3361

Ответ 1

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

Например (компилируется, но не полностью протестировано):

// NotifyPipe.h
#ifndef NOTIFYPIPE_H_INCLUDED
#define NOTIFYPIPE_H_INCLUDED

class NotifyPipe
{
        int m_receiveFd;
        int m_sendFd;

    public:
        NotifyPipe();
        virtual ~NotifyPipe();

        int receiverFd();
        void notify();
};

#endif // NOTIFYPIPE_H_INCLUDED

// NotifyPipe.cpp

#include "NotifyPipe.h"

#include <unistd.h>
#include <assert.h>
#include <fcntl.h>

NotifyPipe::NotifyPipe()
{
    int pipefd[2];
    int ret = pipe(pipefd);
    assert(ret == 0); // For real usage put proper check here
    m_receiveFd = pipefd[0];
    m_sendFd = pipefd[1];
    fcntl(m_sendFd,F_SETFL,O_NONBLOCK);
}


NotifyPipe::~NotifyPipe()
{
    close(m_sendFd);
    close(m_receiveFd);
}


int NotifyPipe::receiverFd()
{
    return m_receiveFd;
}


void NotifyPipe::notify()
{
    write(m_sendFd,"1",1);
}

Затем select с receiverFd() и уведомите о завершении с помощью notify().

Ответ 2

Закройте сокет, используя вызов shutdown(). Это запустит любые потоки, заблокированные на нем, сохраняя дескриптор файла действительным.

close() в дескрипторе, который использует другой поток B, опасен по своей природе: другой поток C может открыть новый файловый дескриптор, который затем будет использовать нить B вместо закрытого. dup2() a /dev/null на него избегает этой проблемы, но не надежно защищает потоки.

Обратите внимание, что shutdown() работает только в сокетах - для других типов дескрипторов вам, вероятно, понадобятся подходы select + pipe-to-self или cancelation.

Ответ 3

Закройте прослушивающий сокет, и accept вернет ошибку.

Что не работает с этим? Опишите проблемы, с которыми вы сталкиваетесь.

Ответ 4

pthread_cancel для отмены потока, заблокированного в accept(), является рискованным, если реализация pthread не реализует отмену должным образом, то есть если поток создал сокет, перед тем как вернуться к вашему коду, для него вызывается pthread_cancel() поток отменяется, и вновь созданный сокет просачивается. Хотя FreeBSD 9.0 и более поздние версии не имеют такой проблемы с условиями гонки, но сначала вы должны проверить свою ОС.