【lesson15】进程控制之进程等待(2)

2023-12-15 20:38:59

进程等待

上次我们讲了许多关于进程等待的必要性和重要性还有其函数的使用方法,这次我们主要介绍函数细节方面的问题。

阻塞式等待

代码演示进程阻塞式等待
代码:

#include <stdlib.h>    
#include <sys/types.h>    
    
int main()    
{    
    
  pid_t id = fork();    
  if(id == 0)    
  {    
    while(1)    
    {    
      printf("I am child id:%d\n",getpid());    
      sleep(1);    
    }    
    exit(2);    
  }    
  else if(id > 0)    
  {    
    printf("I am father id:%d\n",getpid());    
    int status = 0;    
    pid_t result = waitpid(id,&status,0);                                                                      
    if(result > 0)    
    {    
        printf("father waitpid success, exit_code:%d, exit_signal:%d\n",(status>>8)&0xFF,status&0x7F);    
    }    
  }    
  else    
  {    
    printf("create child process fail");    
    exit(1);    
  }    
  return 0;    
} 

演示:用三个窗口观察
窗口2:每个一秒监控一下进程
窗1:运行代码
窗口0:发送信号杀死子进程,查看父进程是否会等待成功

监控指令:

 while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; echo "################################################################"; done

在这里插入图片描述
而这时我们杀掉子进程
在这里插入图片描述
这时我们可以看到,父进程等待工程,然后也随之退出,但是这里的等待是阻塞式等待,等待的时候父进程啥事也做不了

非阻塞式等待

那么如何非阻塞式等待呢?
在这里插入图片描述
我们会看到waitpid中有options
0:为阻塞式等待
WANOHANG:为非阻塞式等待

那么WANOHANG时什么呢?
我们知道Linux的内核是用C语言写的,而系统调用接口是Linux自己实现的并且提供的,所以这些接口全是C语言函数,所以Linux一般提供的大写标记位如:WNOHANG,其实就是宏定义。
证明:
在这里插入图片描述

非阻塞式等待 vs 阻塞式等待

阻塞式等待理解:我们应该都经历过手机或者电脑要打开某应用时突然就很卡,有时肯就卡在那了,怎么按都没有反应,在系统层面就是该进程没有被CPU调度。这时可能CPU很忙,所以这个进程要么是在阻塞对列中,要么就是在等待被调度。

非阻塞式等待理解:如果父进程通过调用waitpid来进行等待,检测到该子进程没有退出,waitpid这个系统调用立马返回。
waitpid的大概执行逻辑:
在这里插入图片描述
进程阻塞本质是阻塞在系统内部,进程阻塞时后面代码就不会再执行了。
当条件满足时父进程会被唤醒,那么是从哪里开始唤醒的?
在这里插入图片描述
所以waitpid重新调用还是从if的后面开始运行
非阻塞调用是如果子进程还没退出就直接return 0
在这里插入图片描述
阻塞一般都是在内核中阻塞,然后等待被唤醒。

讲个故事来理解阻塞等待和非阻塞等待
故事背景:华清中学的期中考要开始了,大家都在着手备考。
人物:
在这里插入图片描述
故事开始:
要期中考了,小华一个字都没学,他准备请小明吃饭,然后找小明去要课堂笔记顺便让他给自己画个重点。
于是小华就去到小明的宿舍楼下,热后打电话给小明
在这里插入图片描述
于是小华等啊等,等了几乎2个小时左右,话费都要扣光了,最后小明回复说要下来了,小华喜极而泣。
最后在小明的帮助下小华,顺利的通过了期中考。

过来几个月,华清中学的期末考来了,小明又没学习于是他又到小明宿舍楼下,准备请小明吃饭顺便找小明去要课堂笔记,也让他给自己画个重点。
在这里插入图片描述
小华又要等,但是小华想起了上次的经历,于是汲取了上次的教训。
在这里插入图片描述
之后小华就在楼下等待同时开启了游戏,每一把游戏结束,小华就打电话给小明一次,问他好了没,玩了几把游戏之后,小明终于做完了作业,这时小华也打电话给了小明,小明回复小华说好了。于是两人就一起去吃饭。

类比:
小华:用户
打电话:系统接口调用
小明:操作系统

第一次小华的电话一直截图就是:阻塞式等待
第二次小华没结束一把游戏之后打一个电话给小明就是:基于非阻塞调用的轮询检测方案。

那么为什么要学阻塞和非阻塞?
因为我们未来编写的代码内容或者网络代码大部分都是IO类别,会不断的面临阻塞和非阻塞接口!

更改代码为非阻塞等待:

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

int main()
{

  pid_t id = fork();
  if(id == 0)
  {
    while(1)
    {
      printf("I am child id:%d\n",getpid());
      sleep(1);
    }
    exit(2);
  }
  else if(id > 0)
  {
    /*printf("I am father id:%d\n",getpid());
    int status = 0;
    pid_t result = waitpid(id,&status,0);
    if(result > 0)
    {
        printf("father waitpid success, exit_code:%d, exit_signal:%d\n",(status>>8)&0xFF,status&0x7F);
    }*/
    int quit = 0;                                                                                                                                                       
    while(!quit)
    {
      int status = 0;
      pid_t res = waitpid(-1,&status,WNOHANG);
      if(res > 0)
      {
        printf("等待子进程成功,退出码:%d\n",WEXITSTATUS(status));
        quit = 1;
      }
      else if(res == 0)                                                                                                                                                 
      {
        printf("子进程还在运行中,暂时还没有退出,父进程可以再等一下,处理一下其它事情??\n");
        sleep(1);
      }
      else 
      {
        printf("wait 失败\n");
        quit = 1;
      }
    }
  }
  else 
  {
    printf("create child process fail");
    exit(1);
  }
  return 0;
}

运行测试:
在这里插入图片描述
我们可以看到,父进程可以做自己的是,跟之前的阻塞式等待不一样了。
在这里插入图片描述
我们看到用信号杀掉了子进程,父进程也回收了子进程
那么非阻塞式等待具体用法呢?
可以用来派发任务
更改代码为可执行任务的版本:

#include <iostream>
#include <vector>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <sys/types.h>

typedef void (*handler_t) ();//函数指针
std::vector<handler_t> handlers;
void fun_one()
{
  printf("这是临时任务1\n");
}

void fun_tow()
{
  printf("这是临时任务2\n");
}

void load()
{
  handlers.push_back(fun_one);
  handlers.push_back(fun_tow);
}

int main()
{
                                                                                                                                                                        
  pid_t id = fork();
  if(id == 0)
  {
    while(1)
    {
      printf("I am child id:%d\n",getpid());
      sleep(1);
    }
    exit(2);
  }                                                                                                                                                                     
  else if(id > 0)
  {
    /*printf("I am father id:%d\n",getpid());
    int status = 0;
    pid_t result = waitpid(id,&status,0);
    if(result > 0)
    {
        printf("father waitpid success, exit_code:%d, exit_signal:%d\n",(status>>8)&0xFF,status&0x7F);
    }*/
    int quit = 0;
    while(!quit)
    {
      int status = 0;
      pid_t res = waitpid(-1,&status,WNOHANG);
      if(res > 0)
      {
        printf("等待子进程成功,退出码:%d\n",WEXITSTATUS(status));
        quit = 1;
      }
      else if(res == 0)
      {
        printf("子进程还在运行中,暂时还没有退出,父进程可以再等一下,处理一下其它事情??\n");
        if(handlers.empty()) load();
        for(auto iter: handlers)
        {
          iter();
          //sleep(1);
        }
        sleep(1);
      }
      else 
      {
        printf("wait 失败\n");
        quit = 1;
      }
    }
  }
  else 
  {
    printf("create child process fail");
    exit(1);
  }
  return 0;
}

运行演示:
在这里插入图片描述
这样以后想让父进程闲了执行任何任务的时候,只要向load里面插入任务函数地址,就可以让父进程执行对应的任务。

文章来源:https://blog.csdn.net/m0_67077469/article/details/135021148
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。