线程和fork: fgetc()在读取popen()-pipe时阻塞
Threads and fork: fgetc() blocks when reading from popen()-pipe
在一个多线程程序(运行在ARM上)我有
一个主线程,它定期检查popen( "pidof -s prog" )
是否有另一个程序正在运行。我使用O_CLOEXEC
标志作为文件描述符,并检查fgetc()
是否从管道接收任何内容。将文件描述符设置为"non-blocking"将导致没有任何内容被读取,并且对没有帮助。与shell命令相同的pidof
命令执行良好。
在另一个线程中,fork()
与子进程中的execl()
一起用于在特定事件发生时启动rsync
操作。父进程使用一个信号处理程序来观察子进程的状态,并且可以选择在另一个特定的事件中终止子进程。不管我是用rsync
还是sleep
调用exec()
,结果都是一样的。
问题是主线程中的fgetc()
阻塞直到子进程终止。
我将尝试通过早期fork()
来解决这个问题(在应用程序是单线程的时候,就像我开始的另一篇文章中假设的那样)。
但无论如何:
我想了解从管道读取时导致fgetc()
阻塞的原因。
到目前为止,我尝试了一些事情:
- 我试图用一个小的示例应用程序重现这个问题,它做了我上面描述的,并希望它会显示相同的错误行为,但不幸的是它工作得很好,这就是为什么我没有在这里提供任何代码。也许我没有抓住重点。
- 通过
system()
使用相同的rsync
调用不会导致任何问题 -
我已经看了
system()
的实现,可以看到在fork()
ing之前信号被操纵:- SIGCHLD被阻塞
- SIGINT和SIGQUIT被忽略
我需要SIGCHLD的信号处理程序,但出于好奇,我试图做同样的代码从上面(我用
pthread_sigmask()
代替sigprocmask()
) -没有任何成功,行为保持不变。我无法在我的BSP提供的源代码中找到任何
system()
的实现。
程序通过fstream
打开其他文件-没有O_CLOEXEC(更改它会有点麻烦)
修复和解释意外行为
的确,我错过了相关的一点。在将示例程序更适应于原始代码示例之后,我发现信号处理程序(在测试程序中工作)是问题所在。摘录:
void MyClass::sig_handler(int sig) {
if( m_pid < 1 ) // not the child we're waiting for
return;
pid_t pid;
int wstatus;
while ((pid = waitpid( -1, &wstatus, WNOHANG )) != -1 ) {
// error: this returns 0 as long as any children are alive
// -> check for "> 0" to ignore active child processes
if( pid != m_pid )
return;
// handle stuff here...
}
}
我必须替换下面的行
while ((pid = waitpid( -1, &wstatus, WNOHANG )) != -1 )
while ((pid = waitpid( -1, &wstatus, WNOHANG )) > 0 )
因为程序的其他线程fork()
子线程(例如与popen()
一起)。如果这些终止,也调用信号处理程序(静态类函数)。
如我所知:
在我调用fork()
的线程中,我使用具有默认值和重置值-1
的成员m_pid
。它从fork()
获取pid。如果m_pid
为-1,信号处理程序立即返回。
程序在popen()
被阻塞,fork()
被阻塞(可以是fork()
被阻塞的任何其他调用),因此当popen()
返回时进入SIGCHLD的信号处理程序。对m_pid
的检查通过,因为m_pid = fork()
已经被调用。waitpid()
不返回-1,但popen()
子进程的pid,然后继续检查返回值= 0,直到所有子进程都终止-我正在等待的那个仍然活着!只有当waitpid()
返回-1
时,主线程才能继续读取fgetc()
。
手册页来自waitpid
:
如果指定了WNOHANG,并且pid指定了一个或多个子(ren)存在,但尚未更改状态,则返回0。错误时,-1返回
因为签名处理程序检查m_pid != -1
,所以只有当我在MyClass中使用fork()
来设置m_pid
时才会出现问题。
这就是为什么使用system()
不会引起问题。m_pid
没有被设置为值!= -1,因此如果子进程在主线程中被popen()
调用,sig处理程序将立即返回。
system()
调用的模仿失败,因为我将m_pid
设置为fork()
,因此信号处理程序没有立即返回。
我猜由于信号处理程序是static member function
,处理程序阻塞了fork()
创建子进程的线程。