Пробуждение потока заблокировано при вызове accept () - PullRequest
25 голосов
/ 21 марта 2010

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

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

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

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

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

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

  3. Закрытие сокета прослушивания из основного потока для прерывания 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();

}

Ответы [ 4 ]

50 голосов
/ 22 марта 2010

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

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

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

16 голосов
/ 21 марта 2010

Вы можете использовать канал, чтобы уведомить поток, что вы хотите, чтобы он вышел. Тогда у вас может быть вызов 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().

0 голосов
/ 29 марта 2013

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

0 голосов
/ 21 марта 2010

Закройте прослушивающий сокет и подтвердите возврат.

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

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