唤醒线程在accept()调用时被阻塞

Linux上的套接字问题

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

当程序退出的时候,我如何发信号通知此网络工作线程(从主线程)从accept()调用返回,同时仍能正常退出其循环并处理其清理代码。

我试过的一些事情:

  1. pthread_kill发送信号。 感觉很难做到这一点,加上它不能可靠地允许线程执行它的关闭逻辑。 也使程序终止。 如果可能的话,我想避免发出信号。

  2. pthread_cancel可以。 与上面相同。 这是对线程的严厉杀戮。 那个,线程可能正在做其他事情。

  3. 从主线程关闭listen套接字以使accept()中止。 这不可靠。

一些限制:

如果解决方案涉及使监听套接字无阻塞,那很好。 但我不想接受一个解决方案,该解决方案涉及线程每几秒钟通过一次选择呼叫唤醒以检查退出条件。

要退出的线程条件可能与退出的进程无关。

从本质上讲,我想要的逻辑看起来像这样。

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(); } 

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

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

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

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

关闭侦听套接字并接受将返回错误。

什么不能可靠地解决这个问题? 描述您面临的问题。

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