线程和分叉:从 popen()-pipe 读取时 fgetc() 阻塞
Posted
技术标签:
【中文标题】线程和分叉:从 popen()-pipe 读取时 fgetc() 阻塞【英文标题】:Threads and fork: fgetc() blocks when reading from popen()-pipe 【发布时间】:2016-08-17 13:17:41 【问题描述】:在一个多线程程序(在 ARM 上运行)我有
一个主线程,除其他外,它定期检查 popen( "pidof -s prog" )
是否有另一个程序正在运行。我对文件描述符使用O_CLOEXEC
标志并检查fgetc()
是否从管道接收到任何内容。 将文件描述符设置为“非阻塞”不会导致读取任何内容,也无济于事。来自 shell 命令的相同 pidof
命令运行良好。
在另一个线程中,子进程中带有立即execl()
的fork()
用于在发生特定事件时启动rsync
操作。父母使用信号处理程序来观察孩子的状态,并可以选择在另一个特定事件中杀死孩子。不管我是用rsync
还是sleep
调用exec()
,结果都是一样的。
问题是主线程中的fgetc()
一直阻塞,直到子进程终止。
我将尝试通过fork()
ing 及早解决这个问题(在应用程序是单线程的某个时候,正如我开始的another post 所假设的那样)。
但无论如何:
我想了解从管道读取时导致fgetc()
阻塞的原因。
到目前为止我尝试过的一些事情:
我尝试使用一个小型示例应用程序来重现该问题,该应用程序执行上述操作,并希望它会显示相同的错误行为,但不幸它工作正常,这就是我这样做的原因此处暂不提供任何代码。也许我错过了相关点。 通过system()
使用相同的rsync
调用不会导致任何问题
我查看了system()
implementation,可以看到信号在fork()
ing 之前被操纵:
我需要 SIGCHLD 的信号处理程序,但出于好奇,我尝试在上面的代码中执行相同的操作(我将 sigprocmask()
替换为 pthread_sigmask()
) - 没有任何成功,行为保持不变。
我在 BSP 提供的源代码中找不到 system()
的任何实现。
程序通过fstream
打开其他文件-并且没有O_CLOEXEC(会有点cumbersome to change that)
【问题讨论】:
system
与内核源代码无关。它在你的 libc 中。
没错,我已经改了
您的问题是什么?假设它是“从管道读取时导致fgetc()
阻塞的原因”,您的编辑现在解决了吗?如果是这样,最好将其移出答案并将其标记为已接受。
我已经发布了编辑,所以人们现在不要再浪费时间来回答这个问题了。它似乎已修复,但我想在将其发布为答案之前进行一些测试以确保。
【参考方案1】:
错误修正和意外行为解释
确实,我错过了相关点。在将示例程序更多地改编为原始代码示例后,我发现信号处理程序(在测试程序中工作)是问题所在。摘录:
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()
的线程中,我使用一个成员m_pid
,默认值和重置值-1
。它从fork()
获取pid。如果 m_pid
为 -1,则 sig 处理程序立即返回。
程序在popen()
阻止fork()
s(可能是fork()
s 的任何其他调用)。因此,当popen()
返回时,将输入 SIGCHLD 的信号处理程序。 m_pid
的检查已通过,因为 m_pid = fork()
已被调用。 waitpid()
不返回 -1,而是返回 popen()
孩子的 pid,然后继续检查返回值 = 0,直到所有孩子都终止 - 我正在等待的孩子还活着!只有这样waitpid()
返回-1
,主线程才能继续读取fgetc()
。
来自waitpid
的手册页:
如果指定了 WNOHANG 并且 pid 指定了一个或多个子项 存在,但尚未改变状态,则返回 0。出错时,-1 被退回
因为 sig 处理程序会检查 m_pid != -1
,所以只有当我在 MyClass 中使用 fork()
设置 m_pid
时才会出现问题。
这就是为什么使用system()
不会导致问题的原因。 m_pid
未设置为值!= -1,因此 sig 处理程序会立即返回,例如一个孩子在主线程中是popen()
ed。
system()
调用的模仿失败,因为我将m_pid
设置为fork()
,因此sig 处理程序没有立即返回。
我猜因为 sig 处理程序是 static member function
,所以处理程序会阻塞 fork()
ed 子进程的线程。
【讨论】:
以上是关于线程和分叉:从 popen()-pipe 读取时 fgetc() 阻塞的主要内容,如果未能解决你的问题,请参考以下文章
在 linux 中,使用 pipe() 从分叉进程调用 system()
实时 subprocess.Popen 通过 stdout 和 PIPE