非阻塞轮询

news2024/9/20 11:31:33

目录

  • 前言
  • 1.options 参数
  • 2. 非阻塞轮询
  • 3. 模拟非阻塞轮询
  • 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 即可。

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

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

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


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

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

感谢各位观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2082466.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

numpy 中的降维与升维

升维&#xff08;Dimensionality Augmentation&#xff09;&#xff1a;增加数据的维度&#xff0c;通常用于提供更多信息或从不同的角度看待数据。 降维&#xff08;Dimensionality Reduction&#xff09;&#xff1a;减少数据的维度&#xff0c;通常用于简化数据或去除无关紧…

微信删除了好友如何恢复?试试这3种方法,赶紧收藏!shuju

推荐3种恢复微信聊天记录的方法&#xff0c;帮你找回微信好删除好友的聊天记录&#xff01; 操作简单&#xff0c;可用于iOS、安卓手机和PC电脑端 1、万兴数据管家 点击直达官网下载>>https://huifu.wondershare.cn 万兴数据管家是一款专业的微信数据恢复软件。 支持苹…

免费屏幕录制软件有哪些?2024年最新测评15款录屏软件排行榜!

免费屏幕录制软件有哪些?2024年最新测评15款录屏软件排行榜! 录屏软件数不胜数&#xff0c;百度一搜跳出来成百上千款录屏软件&#xff01;专业的用起来太复杂&#xff0c;简单的又不堪其用&#xff01;还有各种各样防不胜防的套路。如果你没有像我一样深度了解过不同录屏软件…

基于huggingface peft进行qwen1.5-7b-chat训练/推理/服务发布

一、huggingface peft微调框架 1、定义 PEFT 是一个为大型预训练模型提供多种高效微调方法的Python库。 微调传统范式是针对每个下游任务微调模型参数。大模型参数总量庞大&#xff0c;这种方式变得极其昂贵和不切实际。PEFT采用的高效做法是训练少量提示参数(Prompt Tuning…

Datawhale X 李宏毅苹果书 AI夏令营 task2

《深度学习详解》 - 自适应学习率&#xff08;Task2&#xff09; 1. 自适应学习率的背景与重要性 学习率的挑战&#xff1a; 在训练深度学习模型时&#xff0c;选择合适的学习率至关重要。过大的学习率会导致训练过程中的震荡&#xff0c;使模型无法收敛&#xff1b;过小的学…

在 Navicat BI 中创建自定义字段:自定义排序顺序

在 Navicat BI 中&#xff0c;数据源引用你连接中的表或文件/ODBC 源中的数据&#xff0c;并可从不同服务器类型的中选择数据。数据集中的字段可用于构建图表。事实上&#xff0c;在构建图表时&#xff0c;你需要指定用于填充图表的数据源。 正如我们在整个系列中所看到的&…

html+css网页设计 个人网站模版 个人博客12个页面

htmlcss网页设计 个人网站模版 个人博客12个页面 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码…

【Material-UI】Radio Group中的独立单选按钮详解

文章目录 一、Radio 组件概述1. 组件介绍2. 基本用法 二、Radio 组件的关键特性1. 选中状态控制2. 关联标签3. 自定义样式和图标4. 使用 FormControlLabel 提供标签支持 三、Radio 组件的实际应用场景1. 表单中的单选题2. 设置选项3. 导航选择 四、注意事项1. 无障碍支持2. 样式…

开源产品GeoMesa、MobilityDB存在哪些不足

友情链接&#xff1a; •时空数据库系列&#xff08;一&#xff09;什么是时空数据&#xff1f;特征和适用场景有哪些&#xff1f; •时空数据库系列&#xff08;二&#xff09;时空数据库介绍 了解数据模型与应用场景 •时空数据库系列&#xff08;三&#xff09;技术讲解&…

Linux网口指令

一 查看配置 ifconfig 二 修改IP sudo ifconfig ens33 192.168.150.100 netmask 255.255.255.0

一键复制模板,乔拓云助力小程序快速上线

选择乔拓云模板开发小程序&#xff0c;成本低且高效&#xff0c;适合各行业快速搭建。注册账号后&#xff0c;进入模板中心&#xff0c;轻松找到匹配行业的模板。模板内容自定义灵活&#xff0c;图片、文字随心修改&#xff0c;右侧编辑区操作直观。 小程序开发步骤概览&#x…

秋招复习笔记——嵌入式裸机开发

底层相关的内容&#xff0c;之前掌握的不扎实&#xff0c;现在重新把相关重点记录一下&#xff0c;做个笔记记诵。 相关基础知识 ST简单内容 用的F103ZET6&#xff0c;72MHz&#xff0c;FLASH是512KB&#xff0c;SRAM是64KB&#xff0c;144个引脚&#xff0c;2基本定时器&am…

Java 入门指南:Java IO流 —— 字符流

何为Java流 Java 中的流&#xff08;Stream&#xff09; 是用于在程序中读取或写入数据的抽象概念。流可以从不同的数据源&#xff08;输入流&#xff09;读取数据&#xff0c;也可以将数据写入到不同的目标&#xff08;输出流&#xff09;。流提供了一种统一的方式来处理不同…

【深入解析】最优控制中的Bellman方程——从决策到最优路径的探索

【深入解析】最优控制中的Bellman方程——从决策到最优路径的探索 关键词提炼 #Bellman方程 #最优控制 #动态规划 #值函数 #策略优化 #强化学习 第一节&#xff1a;Bellman方程的通俗解释与核心概念 1.1 通俗解释 Bellman方程是动态规划中的一个核心概念&#xff0c;它像是…

apache服务器的配置(服务名httpd,端口80 , 443)

目录 前言 配置文件 apache服务器的配置 安装apache服务器 配置防火墙 编辑配置文件 配置虚拟主机 基于域名的虚拟主机 配置dns服务器 将网站文件放到/var/www/目录下 修改主配置文件 新建vhost文件夹和xxx.conf文件 编辑 .conf 文件 检查配置 重启服务并访问网…

VS2022 QT环境显示中文乱码问题

1.问题描述 在VS2022中搭配QT6.2环境&#xff0c;在文本处设置中文&#xff0c;运行程序文本处显示乱码&#xff0c;未成功显示想要的中文。 2.VS2015解决方案 如果是VS2015的话&#xff0c;直接文件->高级保存选项可以设置编码格式。 修改编码格式如图所示&#xff1a;…

2024 Python3.10 系统入门+进阶(九):封装解构和集合Set常用操作详解

目录 一、封装和解构1.1 基本概念1.2 简单解构1.3 剩余变量解构1.4 嵌套解构1.5 其他解构1.6 序列模式匹配&#xff08;Python 3.10 最引人注目的新功能&#xff09;1.6.1 结构模式匹配的核心概念1.6.2 结构模式匹配的优势1.6.3 使用场景 二、集合Set2.1 初始化2.1.1 "{}&…

Java-数据结构-包装类和认识泛型 !!!∑(゚Д゚ノ)ノ

目录&#xff1a; 一、包装类&#xff1a; 1、基本数据类型所对应的包装类&#xff1a; 2、装箱和拆箱&#xff1a; 二、 泛型&#xff1a; 1、什么是泛型&#xff1a; 2、语法&#xff1a; 三、泛型类的使用&#xff1a; 四、裸类型&#xff1a; 五、泛型的擦除机制&…

82、k8s的service-NodePort端口开放和生命周期

0、单节点服务&#xff0c;以及k8s命令 [rootmaster01 ~]# kubectl create deployment nginx1 --imagenginx:1.22 --replicas3[rootmaster01 ~]# kubectl create deployment nginx1 --imagenginx:1.22 ##创建资源 deployment.apps/nginx1 created[rootmaster01 opt]# kubec…

软件设计原则之依赖倒置原则

依赖倒置原则&#xff08;Dependency Inversion Principle, DIP&#xff09;是软件设计中一个非常重要的原则&#xff0c;它属于面向对象设计的SOLID原则之一。这个原则的核心在于通过抽象来降低模块间的耦合度&#xff0c;使得系统更加灵活和可维护。 目录 依赖倒置原则的基本…