上周,项目里需要一个视频转发工具,我用libvlc完成了一个,大致流程如下:
(1)主进程监听指定端口,接收转发请求;
(2)验证请求合法性后,主进程fork一个子进程,在子进程中启动vlc进行转发。
但调试时发现了一个小问题:
fork出子进程后,在父进程中没有调用waitpid,在子进程退出后, 父进程没有为它“收尸”。于是乎,子进程虽然退出了,但进程号在系统历仍然存在,变成了臭名昭著的“僵尸进程”。
问题的根源在于:父进程没有调用waitpid来监测子进程的活动状况,关于waitpid能干什么,请搜索之。。。
但是:父进程是单线程(称为监听线程),主要干一件事,那就是调用socket的recvfrom(),阻塞的监听网络请求; 因此,不可能用来调用waitpid来为子进程收尸。
有四个办法解决:
(一)在父进程的监听线程中给socket设超时,每次超时后就调用waitpid(使用NOHANG参数,免得主进程被挂住)来查看一下子进程的状态。如果子进程退出了,就为它收尸;否则就继续recrfrom,监听请求。
主要问题有两个:(1)子进程退出后,必须等到recvfrom超时后,父进程才会调用waitpid,才会知道子进程已经退出了,从而为它收尸;
(2)虽然调用waitpid时间很短,但也会造成recvfrom()暂时中断;如果这段时间刚好有网络请求过来,父进程就很可能接收不到该请求,造成服务无响应,这种情况必须避免。
(二)父进程中再开一个线程(简称监测线程),使用waitpid()专门检测子进程的状态(不使用NOHANG参数,一直挂在waitpid上),实际上我在录制程序中就是这么干的(主要是为了实现子进程崩溃时,由父进程自动重启子进程)。
问题在于:在监测线程中检测到子进程退出后,我还需要从父进程中查找子进程列表,确认是那一路节目退出了;这样就必须加锁,因为监听线程随时可能访问子进程列表。
这样一来,就遇到了棘手的问题:多线程+加锁+fork()多进程,这正是我尽力避免的一种情况 。
(三) 在父进程中忽略SIGCLD信号,就不会产生僵尸进程了,具体如下:
signal(SIGCLD, SIG_IGN); /* now I don't have to wait()! */
但是,当我在子进程中启动libvlc 进行视频转发时,发现无法启动vlc,经过跟踪,确认子进程在初始化libvlc时挂住了:
_vlcinstance = libvlc_new(sizeof (vlc_args) / sizeof (vlc_args[0]), vlc_args, &_vlcexcep); //Calculation character space
具体就是调用libvlc_new()时,原因未知,推测可能是libvlc内部对该信号量做了某种处理,导致挂死。
(四)父进程中,连续fork()两次,立即退出子进程,使孙进程成为“孤儿进程”,被系统的init进程管理;这样,孙进程退出时,init进程会负责为它收尸,就不会产生僵尸进程了。
虽然听起来不错,但总归有点投机取巧。
最后权衡了一下,还是选择了方法(二)来解决此问题。
原因是:
(1)我希望将自动重启机制也加进来。由于vlc本身并不是很稳定,在运行时可能会崩溃;而 我们的转发服务不能停。
因此,如果主进程发现某一路子进程退出了,立即检查是否需要重启(有多种退出原因,可能是主动停止的),如果需要,就查找是哪一路节目,然后立即fork()出一个子进程进行转发。
(2)至于多线程加锁的问题, 后来确认了一下,实际上并不存在此问题。因为在父进程的监测线程中并不需要对子进程列表进行写操作,只需要查询即可.