多进程编程之如何避免僵尸进程
考虑子进程先于父进程结束的情况:
- 若父进程未处理子进程退出状态,在父进程退出前,子进程一直处于僵尸进程状态。
- 若父进程调用waitpid()(这里使用阻塞调用确保子进程先于父进程结束)来等待子进程结束,将会使父进程在调用waitpid()后进入睡眠状态,只有子进程结束父进程的waitpid()才会返回。 如果存在子进程结束,但父进程还未执行到waitpid()的情况,那么这段时期子进程也将处于僵尸进程状态。
由此,可以看出父进程与子进程有父子关系,除非保证父进程先于子进程结束或者保证父进程在子进程结束前执行waitpid(),子进程均有机会成为僵尸进程。那么如何使父进程更方便地创建不会成为僵尸进程的子进程呢?这就要用两次fork()了。
父进程一次fork()后产生一个子进程随后立即执行waitpid(子进程pid, NULL, 0)来等待子进程结束,然后子进程fork()后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其子进程结束时会自动收尸,这样也就不会产生僵尸进程了。
通用场景:
一个进程要创建一个进程,两个进程同时处理任务,谁也不耽误谁。如果直接用子进程充当第二个进程的角色,那么问题是这样的:如果父进程处理时间长,子进程处理时间短,那么如果父进程不 wait() 处理的话,子进程就会成为僵尸进程,但如果父进程 wait() 子进程的话,父进程就会阻塞,所有有个方法就是让自己尽快推出,任务让子进程的子进程来处理。
1 #include <unistd.h> 2 #include <sys/wait.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 int main() 7 { 8 pid_t pid; 9 if( (pid = fork()) < 0 ) 10 { 11 printf("fork error.\n"); 12 exit(-1); 13 } 14 else if(pid == 0) /* first child */ 15 { 16 if( (pid = fork()) < 0 ) 17 { 18 printf("fork error.\n"); 19 exit(-1); 20 } 21 else if(pid > 0) 22 { 23 exit(0); 24 } 25 26 /* We're the second child; our parent becomes init as soon as our real parent exits. */ 27 printf("second child, parent pid = %d\n", getppid()); 28 /* ---------------handle tasks--------------- */ 29 exit(0); 30 } 31 32 if(waitpid(pid, NULL, 0) != pid) /* wait for first child */ 33 { 34 printf("waitpid error.\n"); 35 exit(1); 36 } 37 printf("parent, first child pid = %d\n", pid); 38 /* ---------------handle tasks--------------- */ 39 40 exit(0); 41 }
2、通过信号机制来避免僵尸进程
1)在父进程fork()之前安装SIGCHLD信号处理函数,并在此handler函数中调用waitpid()等待子进程结束,这样,内核才能获得子进程退出信息从而释放那个进程描述符;
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 #include <signal.h> 6 7 static void sig_child(int signo); 8 9 int main() 10 { 11 pid_t pid; 12 //创建捕捉子进程退出信号 13 signal(SIGCHLD,sig_child); 14 pid = fork(); 15 if (pid < 0) 16 { 17 perror("fork error:"); 18 exit(1); 19 } 20 else if (pid == 0) 21 { 22 printf("I am child process,pid id %d.I am exiting.\n",getpid()); 23 exit(0); 24 } 25 printf("I am father process.I will sleep two seconds\n"); 26 //等待子进程先退出 27 sleep(2); 28 //输出进程信息 29 system("ps -o pid,ppid,state,tty,command"); 30 printf("father process is exiting.\n"); 31 return 0; 32 } 33 34 static void sig_child(int signo) 35 { 36 pid_t pid; 37 int stat; 38 //处理僵尸进程 39 while ((pid = waitpid(-1, &stat, WNOHANG)) >0) 40 printf("child %d terminated.\n", pid); 41 }
输出结果:
(^_^)root@rdenv-100#./a.out
I am father process.I will sleep two seconds
I am child process,pid id 9878.I am exiting.
child 9878 terminated.
PID PPID S TT COMMAND
9764 9762 S pts/2 -zsh
9877 9764 S pts/2 ./a.out
9879 9877 R pts/2 ps -o pid,ppid,state,tty,command
father process is exiting.
2)设置SIGCHLD信号为SIG_IGN(即,忽略SIGHLD信号),系统将不产生僵尸进程。通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
比如:对于服务器进程,如果父进程不等待子进程就结束,子进程将成为僵尸进程;若父进程等待子进程结束,就会影响服务器进程的并发性能。所以此时一般就将SIGCHLD信号设置为 SIG_IGN.
注意:当我们在父进程中添加了signal(SIGCHLD,SIG_IGN)时,就不要调用waitpid函数去回收子进程了,否则会报错。man手册中有这样一段话:for waitpid() or waitid()) The process specified by pid (waitpid()) or idtype and id (waitid()) does not exist or is not a child of the calling process. (This can happen for one's own child if the action for SIGCHLD is set to SIG_IGN. See also the Linux Notes section about threads.
使用SIG_IGN信号引起的问题:http://blog.csdn.net/taolinke/article/details/8057335