【问题标题】:Wake up thread blocked on accept() call唤醒线程在 accept() 调用时被阻塞
【发布时间】:2011-01-29 23:45:16
【问题描述】:

Linux 上的套接字问题

我有一个工作线程在接受() 调用时被阻塞。它只是等待传入的网络连接,处理它,然后返回侦听下一个连接。

当程序退出时,我如何向这个网络工作线程(来自主线程)发出信号,使其从 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();

}

【问题讨论】:

  • pthread_kill() 向线程发送信号,仅此而已。而且它不一定“使程序终止”。
  • "pthread_cancel。同上。" Err,什么?不,pthread_cancel() 做的事情与pthread_kill() 完全不同!请 RTFM。
  • 让accept() 返回的一种简单方法是打开一个TCP 连接到accept() 调用正在侦听的端口。

标签: c sockets pthreads


【解决方案1】:

如果 pthread 实现没有正确实现取消,则 pthread_cancel 取消在 accept() 中阻塞的线程是有风险的,也就是说,如果线程创建了一个套接字,就在返回到您的代码之前,会为它调用 pthread_cancel(),线程被取消,新创建的socket被泄露。虽然 FreeBSD 9.0 及更高版本没有这种竞争条件问题,但您应该先检查您的操作系统。

【讨论】:

    【解决方案2】:

    您可以使用管道通知线程您希望它退出。然后你可以有一个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);
    }
    

    然后selectreceiverFd(),并使用notify() 通知终止。

    【讨论】:

    • 我很好奇这个,没有经常使用管道。您是否有可以发布的代码来证明这一点? :)
    • 谢谢。当我发布问题时,我实际上正在考虑这种方法。它很干净,效果很好。
    【解决方案3】:

    使用shutdown() 调用关闭套接字。这将唤醒所有阻塞在其上的线程,同时保持文件描述符有效。

    close() 在另一个线程 B 正在使用的描述符上本质上是危险的:另一个线程 C 可能会打开一个新的文件描述符,然后线程 B 将使用该文件描述符而不是关闭的描述符。 dup2() /dev/null 可以避免这个问题,但不能可靠地唤醒阻塞的线程。

    请注意,shutdown() 仅适用于套接字——对于其他类型的描述符,您可能需要 select+pipe-to-self 或取消方法。

    【讨论】:

      【解决方案4】:

      关闭监听socket,accept会返回错误。

      什么不能可靠地使用它?描述您面临的问题。

      【讨论】:

      • 在套接字上调用 close 不会唤醒另一个线程上的阻塞接受调用。我在 Linux 上试过 - 它不起作用。虽然,我相信这种技术适用于 Windows,这就是我为唤醒 IOCP 线程所做的工作。
      猜你喜欢
      • 2017-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-16
      • 1970-01-01
      • 1970-01-01
      • 2022-08-12
      • 1970-01-01
      相关资源
      最近更新 更多