我有一个在后台运行的线程,它以阻塞的方式从输入设备读取事件,现在当我退出应用程序时我想正确地清理线程,但是我不能只运行pthread_join()因为线程由于阻塞IO,它永远不会退出.
我该如何正确解决这种情况?我应该发送pthread_kill(theard,SIGIO)还是pthread_kill(theard,SIGALRM)来打破阻塞?是其中任何一个甚至是正确的信号?或者有另一种方法来解决这种情况,并让该子线程退出阻塞读取?
目前有点困惑,因为我的谷歌搜索没有找到解决方案.
这是在Linux上并使用pthreads.
编辑:我用SIGIO和SIGALRM玩了一下,当我没有安装信号处理程序时他们打破了阻塞IO,但在控制台上给出了一条消息("I/O可能")但是当我安装一个信号处理程序时,为了避免该消息,它们不再破坏阻塞IO,因此线程不会终止.所以我回到了第一步.
执行此操作的规范方法是pthread_cancel
,线程已完成pthread_cleanup_push
/ pop
为其正在使用的任何资源提供清理.
不幸的是,这不能在C++代码中使用.任何C++ std lib代码,或者try {} catch()
当时调用堆栈上的任何内容pthread_cancel
都可能会使整个进程失效.
唯一的解决方法是处理SIGUSR1
,设置停止标志,pthread_kill(SIGUSR1)
然后在I/O上阻塞线程的任何地方,如果EINTR
在重试I/O之前检查停止标志.在实践中,这并不总是在Linux上成功,不知道为什么.
但在任何情况下,它也没用谈谈,如果你有打电话给任何第三方的lib,因为他们将最有可能有一个紧密的循环,简单地重启对I/O EINTR
.反向工程他们的文件描述符以关闭它也不会削减它 - 他们可能正在等待信号量或其他资源.在这种情况下,根本不可能编写工作代码,句点.是的,这完全是脑损伤.与设计C++异常的人交谈pthread_cancel
.据推测,这可以在将来的C++版本中修复.祝你好运.
我也建议使用选择或其他一些非基于信号的方法来终止你的线程.我们有线程的原因之一是试图摆脱信号疯狂.那说......
通常,使用带有SIGUSR1或SIGUSR2的pthread_kill()向线程发送信号.其他建议的信号 - SIGTERM,SIGINT,SIGKILL - 具有您可能不感兴趣的进程范围的语义.
至于发送信号时的行为,我的猜测是它与你处理信号的方式有关.如果未安装处理程序,则应用该信号的默认操作,但是在接收信号的线程的上下文中.例如,SIGALRM将由您的线程"处理",但处理将包括终止进程 - 可能不是所需的行为.
线程接收信号通常会将其从EINTR的读取中分离出来,除非它确实处于前面回答中提到的那种不可中断状态.但我认为不是,或者您使用SIGALRM和SIGIO的实验不会终止该过程.
您的阅读是否可能在某种循环中?如果读取以-1返回终止,则跳出该循环并退出该线程.
你可以玩这个非常草率的代码来测试我的假设 - 我现在距离我的POSIX书有几个时区......
#include#include #include #include int global_gotsig = 0; void *gotsig(int sig, siginfo_t *info, void *ucontext) { global_gotsig++; return NULL; } void *reader(void *arg) { char buf[32]; int i; int hdlsig = (int)arg; struct sigaction sa; sa.sa_handler = NULL; sa.sa_sigaction = gotsig; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); if (sigaction(hdlsig, &sa, NULL) < 0) { perror("sigaction"); return (void *)-1; } i = read(fileno(stdin), buf, 32); if (i < 0) { perror("read"); } else { printf("Read %d bytes\n", i); } return (void *)i; } main(int argc, char **argv) { pthread_t tid1; void *ret; int i; int sig = SIGUSR1; if (argc == 2) sig = atoi(argv[1]); printf("Using sig %d\n", sig); if (pthread_create(&tid1, NULL, reader, (void *)sig)) { perror("pthread_create"); exit(1); } sleep(5); printf("killing thread\n"); pthread_kill(tid1, sig); i = pthread_join(tid1, &ret); if (i < 0) perror("pthread_join"); else printf("thread returned %ld\n", (long)ret); printf("Got sig? %d\n", global_gotsig); }
select()
即使它很少,你也可以暂停,以便在特定条件下优雅地退出线程.我知道,民意调查很糟糕......
另一种方法是为每个子节点创建一个管道,并将其添加到线程正在监视的文件描述符列表中.当您希望该子项退出时,从父项向管道发送一个字节.不以每个线程的管道为代价进行轮询.
取决于它如何等待IO.
如果线程处于"Uninterruptible IO"状态(顶部显示为"D"),那么你真的无能为力.线程通常只是简单地进入这种状态,做一些事情,比如等待页面被交换(或者需求加载,例如来自mmap'd文件或共享库等),但是失败(特别是NFS服务器)可能导致它会在那个州停留更长时间.
真的没有办法摆脱这种"D"状态.线程不会响应信号(您可以发送它们,但它们将排队).
如果它是普通的IO函数,如read(),write()或者像select()或poll()这样的等待函数,信号将正常传递.
随着事态的发展,很可能得到新答案的老问题现在可用于更好地处理线程中的信号.
从Linux内核2.6.22开始,系统提供了一个新函数signalfd()
,可以用来打开给定的一组Unix信号的文件描述符(在完全杀死进程的那些信号之外).
// defined a set of signals sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); // ... you can add more than one ... // prevent the default signal behavior (very important) sigprocmask(SIG_BLOCK, &set, nullptr); // open a file descriptor using that set of Unix signal f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
现在,您可以使用poll()
或select()
函数来侦听您正在侦听的更常见的文件描述符(套接字,磁盘上的文件等)上的信号.
如果你想要一个可以反复检查信号和其他文件描述符的循环(即它对你的其他文件描述符也很重要),NONBLOCK很重要.
我有这样的实现,它适用于(1)定时器,(2)套接字,(3)管道,(4)Unix信号,(5)常规文件.实际上,真的是任何文件描述符加上计时器.
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.cpp
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites /snap_communicator.h
您可能也对libevent这样的图书馆感兴趣