非阻塞轮询

目录

  • 前言
  • [1.options 参数](#1.options 参数)
  • [2. 非阻塞轮询](#2. 非阻塞轮询)
  • [3. 模拟非阻塞轮询](#3. 模拟非阻塞轮询)
  • [4. 非阻塞轮询 + 执行其它任务](#4. 非阻塞轮询 + 执行其它任务)

前言

继上一篇文章 详谈进程等待 讲到 waitpid 系统调用,在该系统调用接口中还有一个 options 参数,本篇文章介绍 watipid 系统调用中的options 参数 以及 什么是非阻塞轮询,非阻塞轮询的同时是如何执行其它任务的。

1.options 参数

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

当父进程 waitpid 等待子进程时,如果子进程退出了,那么父进程读取其 PCB 中的信号码和退出码返回即可;如果子进程迟迟不退出,那父进程就得一直等下去,进而导致父进程阻塞调用,也即父进程处于阻塞状态。

而 options 选项就是设置父进程等待的方式:

  • 默认0,阻塞等待方式(子进程处于 R 状态,操作系统把父进程状态由 R -> S,然后把父进程投递到子进程 PCB 中维护的等待队列,当子进程退出了,操作系统再将父进程唤醒。换言之,也即父进程一直等待子进程的本质就是阻塞与子进程的等待队列中)
  • 非阻塞轮询 WNOHANG。

2. 非阻塞轮询

将 options 设置为 WNOHANG,就是非阻塞轮询,又即等待的时候不要夯住(调用时系统不返回,进程阻塞,系统无响应等,就称为该系统 或 该接口 被夯住了,也即阻塞的专业词语)。

所以什么是非阻塞呢??

故事线:outlier 大学即将迎来一学期一度的期末考试,你作为学渣,平时不上课,课后不作为,马上要考C语言了,你慌的一批。

于是马上打电话给好朋友张三(张三是一名学霸,课后笔记大师):"张三啊,马上要考试了,带上你的笔记过来辅导一下你爹"。

张三:"儿子,你等会,我在复习,过一会就好了",说完就挂掉电话。

于是你也就等下去了,等了十来分钟,你又打电话问张三好了没,张三回复了再等会。于是你又再等下去。

十分钟后,再打一次。。。。

二十分钟,再打一次,这一次张三说,我看到你了,你今天穿白T 对吧,你二话不说,啪的一下挂掉了电话。因为你穿的是蓝T。

循环往复,最终你们终于踏上去图书馆的道路。

上述的张三就是 操作系统;你就是 用户;打电话的过程就是调用系统调用;打电话的本质就是检查操作系统(张三)的状态;每次打电话得到回复之后立马又挂掉就是 系统调用立马返回;每一次打电话检测时操作系统(张三)没好,用户(你)不会一直占线等待,而是立马返回,这叫做非阻塞!而一直打电话问张三,就是轮询!

非阻塞轮询 = 非阻塞 + 循环

下学期,outlier再次迎来数据结构期末考试,你依旧找到张三,"儿子啊,老地方啊"。张三还是让你等着,他还没准备好。

但这次,你跟张三说:"上次一直打你电话,我也不知道你啥时候能好,这次我就不挂了,你好了,下楼见到我了,再挂吧"

而这种占线等待回复,就叫做 阻塞调用!操作系统一直在执行着某种任何,用户一直在检查操作系统任务的完成情况,检测时,即便操作系统任务没完成,系统调用也不返回,即阻塞调用。

下学年,操作系统考试如期而至。你也听闻操作系统的难处,因此你不敢怠慢,吸取前两次的经历,你觉得不能干等着浪费时间。
因此当张三还没下楼找你时,你就在自己宿舍看书复习,时不时的在打电话问一下张三好了没。

这一次,你学聪明了,一边等待张三,一边做着自己的事情,这就是非阻塞轮询的同时,可以执行其它任务。而纯阻塞调用,是无法执行其它任务的,只能干等着。非阻塞轮询 + 执行其它任务,才是最常见的。

所以 waitpid 返回值 ret > 0 时,即代表等待成功; ret < 0 即等待失败;而大量存在的情况是 ret = 0 的情况,即非阻塞轮询检查状态时,目标子进程还没有就绪(即没有退出),而子进程没有退出,并不代表调用的失败或成功,只不过是等待的条件还没有就绪而已!


3. 模拟非阻塞轮询

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {
        int cnt = 5;
        while(cnt)
        {
            printf("I am child, pid:%d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(1);
    }
    else 
    {
        int status = 0;
        while(1)	// 轮询
        {
            pid_t ret = waitpid(id, &status, WNOHANG);  // 非阻塞
            if(ret > 0)
            {
                if(WIFEXITED(status)) 
                	printf("process is normal, exit_code: %d\n", WEXITSTATUS(status));
                else 
                	printf("the process terminated abnormally! \n");
                break;
            }
            else if(ret == 0)
            {
               printf("the child process has not exited and continues to poll non-blocking\n");
               sleep(1);
            }
            else 
            {
0               printf("wait failed!\n");
               break;
            }
        }
        sleep(1);
    }
    return 0;
}

4. 非阻塞轮询 + 执行其它任务

当 waitpid 等待子进程时,检测到子进程状态还没退出,于是立马返回,这之后是如何执行自己的其它任务的呢??执行任务的同时有没有可能错过子进程退出呢??

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

typedef void(*task_t)();    // 函数指针
task_t tasks[TASK_NUM];     // 函数指针数组

void task1() {  printf("task 1, pid: %d\n", getpid()); }
void task2() {  printf("task 2, pid: %d\n", getpid()); }
void task3() {  printf("task 3, pid: %d\n", getpid()); }

int AddTask(task_t t);

// 任务的管理代码
void InitTask()
{
    for(int i = 0; i < TASK_NUM; i++) tasks[i] = NULL;
    AddTask(task1);
    AddTask(task2);
    AddTask(task3);
}

int AddTask(task_t t)
{
    int pos = 0;
    for(; pos < TASK_NUM; pos++) 
        if(!tasks[pos])
        {
            // 模拟添加任务
            break;
        }

    if(pos == TASK_NUM) return -1;  // 任务队列满,返回-1
    tasks[pos] = t;   // 添加指定任务的函数指针
    return 0;
}

void DelTask() { }
void CheckTask() { }
void UpdateTask() { }

void ExecuteTask()		// 执行任务
{
    for(int i = 0; i < TASK_NUM; i++)
    {
        if(!tasks[i]) continue;
        tasks[i]();     // 回调函数
    }
}

int main()
{
    InitTask();
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {
        int cnt = 5;
        while(cnt)
        {
            printf("I am child, pid:%d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
            cnt--;
            sleep(1);
        }
        exit(1);
    }
    else 
    {
        int status = 0;
        while(1)
        {
            pid_t ret = waitpid(id, &status, WNOHANG);  // 非阻塞
            if(ret > 0)
            {
                if(WIFEXITED(status)) 
                {
                    printf("process is normal, exit_code: %d\n", WEXITSTATUS(status));
                }
                else 
                {
                    printf("the process terminated abnormally! \n");
                }
                break;
            }
            else if(ret == 0)
            {
                ExecuteTask(); 
                usleep(500000);
            }
            else 
            {
               printf("wait failed!\n");
               break;
            }
        }
    }
    return 0;
}

以上就是创建单进程的模拟非阻塞轮询的同时,执行自己的其它任务,如果是创建多进程,只需要将 waitpid 中的 pid 参数设置为 -1(等待任意一个子进程),在等待成功之后,不是立马 break,而是维护一个计数器,等待成功一个子进程,就让计数器 -1,直到 计数器 == 0 然后 break 即可。

而至于会不会有可能父进程在执行其它任务的时候,错过了子进程这个问题,所谓错过,无非就是子进程退出的时候,父进程不会立刻马上立刻对其进行回收,但是当父进程执行完自己的任务回来之后,也会对其进行回收。而一般情况下,非阻塞轮询进行等待子进程,才是父进程此时的主线任务,执行其它任务,只不过是顺手的事,不让父进程干等着罢了,因此这类任务一般都是很轻量级的,所以一般也不必担心会不会执行了其它任务,子进程就没有人回收的问题。并且当子进程多一点时,子进程退出时,父进程晚一点来回收,反而可以集中回收,提高效率。

而至于创建出来的诸多子进程中,哪个进程先被调度,我们并无法知晓。但是,我们必须要知道,最后退出的进程一定是父进程!

为什么?----- 因为所有的子进程是父进程创建出来的,所以父进程有等待所有子进程的职责!父进程需要等待所有子进程退出之后,来回收子进程。这也是进程等待重要的一个原因,在编码得到保障时,父进程最后一个退出,是可以保证释放所有子进程的资源的。


关于非阻塞轮询的内容,小篇介绍到这里,后续文章还会介绍 进程替换

如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

相关推荐
小林熬夜学编程1 小时前
【Linux系统编程】第二十弹---进程优先级 && 命令行参数 && 环境变量
linux·运维·服务器·c语言·开发语言·算法
Spring-wind1 小时前
【linux】 ls命令
linux
Flying_Fish_roe1 小时前
linux-安全管理-文件系统安全
linux·运维·安全
小小工匠1 小时前
Linux - 探秘/proc/sys/net/ipv4/ip_local_port_range
linux·本地端口分配范围
CXDNW2 小时前
【Linux篇】TCP/IP协议(笔记)
linux·网络·网络协议·tcp/ip·计算机网络
_Johnny_2 小时前
linux 使用SSH密钥配置免密登录
linux·服务器·ssh
哆啦叮当2 小时前
Linux下root用户共享conda环境给其他用户
linux·运维·服务器·python·conda
耐心坚持努力�3 小时前
k8s重要知识点
linux·运维·k8s·k8s重要知识点
汪公子4923 小时前
k8s的配置
java·linux·docker
毅凉4 小时前
Linux笔记
linux·c语言·网络·数据库