一、为什么需要进程等待

  • 子进程退出,父进程如果不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如:子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

二、进程等待的方法

wait函数

 #include<sys/types.h>
 #include<sys/wait.h>
 pid_t wait(int* status);

返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL

验证:父进程等待可以解决子进程僵尸问题

1 #include <stdio.h>
  2 #include <string.h>
  3 #include <errno.h>
  4 #include <unistd.h>
  5 #include <stdlib.h>
  6 #include <sys/types.h>
  7 #include <sys/wait.h>
  8 
  9 int main()
 10 {
 11     pid_t id = fork();
 12     if(id == 0)
 13     {// child
 14         int cnt = 5;
 15         while(cnt--)
 16         {
 17             printf("我是子进程: pid: %d\n", getpid());
 18             sleep(1);
 19         }
 20         exit(0); // 终止子进程
 21     }
 22     else if(id > 0)
 23     {// parent
 24        // while(1)
 25        // {
 26        //     printf("我是父进程: pid: %d\n", getpid());
 27        //     sleep(1);
 28        // }
 29        // 回收子进程,等待僵尸
 30        //pid_t rid = wait(NULL);                                                                       
 31        sleep(10);
 32        pid_t rid = wait(NULL);
 33        if(rid == id)
 34        {
 35            printf("pid: %d, wait success!\n",\
 36                   getpid());
 37        }
 38        sleep(5);
 39        exit(0);
 40     }
 41 }

解释一下代码的逻辑:创建子进程后,父进程休眠10秒,子进程5秒后终止进程,变成僵尸进程,再过5秒后,父进程醒来,用wait函数回收子进程,子进程消失,再过5秒后,父进程也终止。

运行结果:

// 创建父子进程
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32139 32138 30068 pts/2    32138 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32144 32143 30068 pts/2    32143 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32149 32148 30068 pts/2    32148 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32154 32153 30068 pts/2    32153 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32159 32158 30068 pts/2    32158 S+    1000   0:00 grep --color=auto myprocess
// 子进程变成僵尸状态,父进程还在sleep
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 Z+    1000   0:00 [myprocess] <defunct>
30068 32164 32163 30068 pts/2    32163 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 Z+    1000   0:00 [myprocess] <defunct>
30068 32169 32168 30068 pts/2    32168 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 Z+    1000   0:00 [myprocess] <defunct>
30068 32174 32173 30068 pts/2    32173 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 Z+    1000   0:00 [myprocess] <defunct>
30068 32179 32178 30068 pts/2    32178 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
32134 32135 32134 29186 pts/1    32134 Z+    1000   0:00 [myprocess] <defunct>
30068 32184 32183 30068 pts/2    32183 S+    1000   0:00 grep --color=auto myprocess
// 父进程醒来,子进程被回收
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32189 32188 30068 pts/2    32188 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32194 32193 30068 pts/2    32193 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32199 32198 30068 pts/2    32198 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32204 32203 30068 pts/2    32203 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
29186 32134 32134 29186 pts/1    32134 S+    1000   0:00 ./myprocess
30068 32209 32208 30068 pts/2    32208 S+    1000   0:00 grep --color=auto myprocess
// 父进程结束
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30068 32214 32213 30068 pts/2    32213 S+    1000   0:00 grep --color=auto myprocess

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
30068 32220 32219 30068 pts/2    32219 S+    1000   0:00 grep --color=auto myprocess

waitpid函数

#include <sys/wait.h>
#include <sys/types.h>

pid_t waitpid(pid_t pid, int *status, int options);

1.参数

pid :pid = -1,等待任意一个子进程。与wait等效

          pid > 0,等待其他进程ID与pid相等的子进程

statue:输出型参数,返回子进程的status

这里介绍两个宏:

  • WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(进程是否正常退出)。
  • WEXITSTATUS(status):若WIFEXITED非零(进程正常退出),提取子进程的退出码。(查看进程的退出码)。

options:模认是0,表示阻塞等待

options可以填的参数:WNOHANG:

  • 各部分含义:“Wait” 对应 Linux 中进程等待相关函数(waitpid())的核心功能,即父进程等待子进程状态变化;“No Hang” 表示 “不挂起、不阻塞”,形象体现该选项的核心作用。
  • 该宏常用在waitpid()系统调用的options参数中,用于实现非阻塞等待。当设置此选项后,若没有子进程退出,waitpid()不会让父进程阻塞等待,而是立即返回 0,方便父进程同时处理其他任务。

WNOHANG:可以理解为:若:pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。

        若:正常结束,则返回该子进程的ID。

2. 返回值

  • 成功:返回状态发生变化的子进程 PID(若 options 含 WNOHANG 且无子进程状态变化,返回 0)。
  • 失败:返回 -1(如无对应子进程、被信号中断等),并设置 errno 标识错误原因。

把waitpid的pid 设置为 -1,options设置为 0 就等同于wait函数

三、获取子进程status

  • wait 和 waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16 比特位):

代码:

下面代码中,子进程退出码是1,程序正常运行结束。

 1 #include <stdio.h>
  2 #include <string.h>
  3 #include <errno.h>
  4 #include <unistd.h>
  5 #include <stdlib.h>
  6 #include <sys/types.h>
  7 #include <sys/wait.h>
  8 
  9 int main()
 10 {
 11     pid_t id = fork();
 12     if(id == 0)
 13     {// child
 14         int cnt = 5;
 15         while(cnt--)
 16         {
 17             printf("我是子进程: pid: %d\n", getpid());
 18             sleep(1);
 19         }
 20         exit(1); // 终止子进程
 21     }
 22     else if(id > 0)
 23     {// parent
 24        // while(1)
 25        // {
 26        //     printf("我是父进程: pid: %d\n", getpid());
 27        //     sleep(1);
 28        // }
 29        // 回收子进程,等待僵尸
 30        //pid_t rid = wait(NULL);                                                                       
 31        sleep(10);
 32        //pid_t rid = wait(NULL);
 33        int status = 0;
 34        pid_t rid = waitpid(id,&status, 0);
 35 
 36        if(rid == id)
 37        {
 38            printf("pid: %d, wait success!,statue:%d\n",\
 39                   getpid(), status);
 40        }
 41        sleep(5);
 42      //  exit(0);
 43     }
 44 }

获得到的status是256。

        这里的status一共有36个bit位,其中高16位不用,只使用低16位,其中,低8位表示进程退出时的信号编号,次8位表示进程退出时的退出码(位图结构)

这里的1是在第9位,所以是2^8 == 256。

退出码:

int exit_code = (status >> 8) & 0xFF; //只保留最低8位,其他位置清零

信号编号:

int exit_sig = status & 0x7F //0111 1111 只保留最低7位,其他位置清零

所以可以改进上面的代码:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    if(id == 0)
    {// child
        int cnt = 5;
        while(1)
        {
            printf("我是子进程: pid: %d\n", getpid());
            sleep(1);
        }
        exit(1); // 终止子进程
    }
    else if(id > 0)
    {// parent
       // while(1)
       // {
       //     printf("我是父进程: pid: %d\n", getpid());
       //     sleep(1);
       // }
       // 回收子进程,等待僵尸
       //pid_t rid = wait(NULL);
       int status = 0;
       pid_t rid = waitpid(id, &status, 0);
       if(rid == id)
       {
           //改进的地方:
           int exit_code = ((status >> 8)&0xFF);
           int exit_sig =  status&0x7F; // 0111 1111
           printf("pid: %d, wait success!, status: %d, exit_code: %d, exit_sig: %d\n",\
                   getpid(), status, exit_code, exit_sig);
       }
    }
}

运行结果:

四、一次性创建多个子进程

先看一个错误案例:

5 int main(int argc, char *argv[])  
 36 {               
 37     if(argc != 2)  
 38     {                                                                                                     
 39         std::cout << "Usage: " << argv[0] << " process_num" << std::endl;                                  
 40         exit(USAGE_ERR);  
 41     }  
 42     int num = std::stoi(argv[1]);                                                                                                         
 43     for(int i = 0; i < num; i++)                                                                                                          
 44     {                                                                                                                                     
 45         pid_t id = fork();                                                                                                                
 46         if(id == 0)                                                                                                                       
 47         {                                                                                                                                 
 48             // child                                                                                                                      
 49             Task();                                                                                                                       
 50             exit(0);                                                                                                                      
 51         }                                                                                                                                 
 52         // father                                                                                                                         
 53         int status = 0;                                                                                                                   
 54         pid_t rid = waitpid(id, &status, 0);
 55         if(rid > 0)
 56         {
 57             printf("我是子进程:%d Exit, exit code:%d\n", rid, WEXITSTATUS(status));
 58         }
 59     }
 60                                                                                                                                                        
 61                                                                                                                                   
 62                                                                                                                                   
 63                                                                                                                                   
 64     return 0;                                                                                                                     
 65 } 

这样写非常奇怪,完全不符合预期。

正确写法:

 int main(int argc, char *argv[])
   36 {
   37     if(argc != 2)
   38     {
   39         std::cout << "Usage: " << argv[0] << " process_num" << std::endl;                                
   40         exit(USAGE_ERR);
   41     }
   42     int num = std::stoi(argv[1]);                                                                                                       
   43     std::vector<pid_t> subs;
   44     // 创建多个子进程                                                                                                                                
   45     for(int i = 0; i < num; i++)
   46     {
   47         pid_t id = fork();
   48         if(id == 0)
   49         {
   50             // child
   51             Task();
   52             exit(0);
   53         }
   54         subs.push_back(id);
   55     }
   56 
   57     // 父进程
   58     // 等待多进程
   59     for(auto &pid : subs)
   60     {
   61         // father
   62         int status = 0;
   63         pid_t rid = waitpid(id, &status, 0);
   64         if(rid > 0)
   65         {
   66             printf("我是子进程:%d Exit, exit code:%d\n", rid, WEXITSTATUS(status));
   67         }
   68     }
   69     
   70 
   71 
   72 
   73     return 0;
   74 }

优化一下,封装成函数:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/wait.h>
  4 #include <sys/types.h>
  5 #include <stdlib.h>
  6 #include <iostream>
  7 #include <vector>
  8 
  9 enum 
 10 {
 11     OK,
 12     USAGE_ERR
 13 };
 14 
 15 void Task()
 16 {
 17     int cnt = 5;
 18     while(cnt--)
 19     {
 20         printf("我是一个子进程, 我在完成Task任务, pid: %d, ppid:%d, cnt: %d\n",   getpid(), getppid(), cnt);
 21         sleep(1);
 22     }
 23 }
 24 
 25 void Hello()
 26 {
 27     int cnt = 5;
 28     while(cnt--)
 29     {
 30         printf("我是一个子进程, 我在完成Hello任务, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
 31         sleep(1);
 32     }
 33 }
 34 typedef void (*callback_t) ();
 35 // 一般而言:
 36 // 参数:
 37 // 输入: const &
 38 // 输出:*                                                                                                                                             
 39 // 输入输出: &
 40 void CreateChildProcess(int num, std::vector<pid_t> *subs, callback_t cb)
 41 {
 42     for(int i = 0; i < num; i++)
 43     {
 44         pid_t id = fork();
 45         if(id == 0)                                                                                                                                    
 46         {
 47             //child
 48             cb();
 49             exit(0);
 50         }
 51         // 父进程
 52         subs->push_back(id);
 53     }
 54 }
 55 
 56 void WaitAllChild(const std::vector<pid_t> &subs)
 57 {
 58     for(const auto &pid : subs)
 59     {
 60         int status = 0;
 61         pid_t rid = waitpid(pid, &status, 0);
 62         if(rid > 0)
 63         {
 64             printf("子进程: %d Exit, exit code: %d\n", rid, WEXITSTATUS(status));
 65         }
 66     }
 67 }
 68 
 69 // 启停多进程的方案
 70 // ./myproc 5 --- 我要创建五个子进程
 71 int main(int argc, char *argv[])
 72 {
 73     if(argc != 2)
 74     {
 75         std::cout << "Usage: " << argv[0] << " process_num" << std::endl;
 76         exit(USAGE_ERR);
 77     }
 78     int num = std::stoi(argv[1]);
 79     std::vector<pid_t> subs;
 80     //std::vector<callback_t> cbs;
 81     // 创建多进程
 82     CreateChildProcess(num, &subs, Hello);
 83     // 父进程
 84     WaitAllChild(subs);
 85 
 86     return OK;
 87 }

运行事例:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐