非阻塞轮询

2024-08-28 22:04
文章标签 阻塞 轮询

本文主要是介绍非阻塞轮询,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 前言
  • 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 10typedef 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;  // 任务队列满,返回-1tasks[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.chinasem.cn/article/1116012

相关文章

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

【即时通讯】轮询方式实现

技术栈 LayUI、jQuery实现前端效果。django4.2、django-ninja实现后端接口。 代码仓 - 后端 代码仓 - 前端 实现功能 首次访问页面并发送消息时需要设置昵称发送内容为空时要提示用户不能发送空消息前端定时获取消息,然后展示在页面上。 效果展示 首次发送需要设置昵称 发送消息与消息展示 提示用户不能发送空消息 后端接口 发送消息 DB = []@ro

springboot体会BIO(阻塞式IO)

使用springboot体会阻塞式IO 大致的思路为: 创建一个socket服务端,监听socket通道,并打印出socket通道中的内容。 创建两个socket客户端,向socket服务端写入消息。 1.创建服务端 public class RedisServer {public static void main(String[] args) throws IOException {

多路转接之select(fd_set介绍,参数详细介绍),实现非阻塞式网络通信

目录 多路转接之select 引入 介绍 fd_set 函数原型 nfds readfds / writefds / exceptfds readfds  总结  fd_set操作接口  timeout timevalue 结构体 传入值 返回值 代码 注意点 -- 调用函数 select的参数填充  获取新连接 注意点 -- 通信时的调用函数 添加新fd到

多线程篇(阻塞队列- LinkedBlockingDeque)(持续更新迭代)

目录 一、LinkedBlockingDeque是什么 二、核心属性详解 三、核心方法详解 addFirst(E e) offerFirst(E e) putFirst(E e) removeFirst() pollFirst() takeFirst() 其他 四、总结 一、LinkedBlockingDeque是什么 首先queue是一种数据结构,一个集合中

多线程篇(阻塞队列- LinkedBlockingQueue)(持续更新迭代)

目录 一、基本概要 1. 构造函数 2. 内部成员 二、非阻塞式添加元素:add、offer方法原理 offer的实现 enqueue入队操作 signalNotEmpty唤醒 删除线程(如消费者线程) 为什么要判断if (c == 0)时才去唤醒消费线程呢? 三、阻塞式添加元素:put 方法原理 图解:put线程的阻塞过程 四、非阻塞式移除:poll方法原理 dequ

数字电路专题:verilog 阻塞赋值和非阻塞赋值

verilog 阻塞赋值 和 非阻塞赋值 “=”阻塞赋值, ”<=”非阻塞赋值。阻塞赋值为执行完一条赋值语句,再执行下一条,可理解为顺序执行,而且赋值是立即执行; 非阻塞赋值可理解为并行执行,不考虑顺序,在 always 块语句执行完成后,才进行赋值。 如下面的阻塞赋值: //代码如下:module top(din,a,b,c,clk);input din;input clk;out

多线程篇(阻塞队列- ArrayBlockingQueue)(持续更新迭代)

目录 一、源码分析 1. 先看个关系图 2. 构造方法 3. 核心属性 4. 核心功能 入队(放入数据) 出队(取出数据) 5. 总结 一、源码分析 1. 先看个关系图 PS:先看个关系图 ArrayBlockingQueue是最典型的有界阻塞队列,其内部是用数组存储元素的, 初始化时需要指定容量大小利用 ReentrantLock 实现线程安全。 在生产者

io本质+io效率本质,5种io模型(介绍,异步/同步区别,阻塞/非阻塞区别)

目录 5种io模型 io引入 io的本质 io效率的本质 模型引入 以钓鱼为例 效率最高的方式 异步io和同步io的区别 阻塞式和非阻塞式io的区别 介绍 阻塞式io ​编辑 非阻塞式io ​编辑 信号驱动式io ​编辑 多路转接/复用 ​编辑 异步io 5种io模型 io引入 io的本质 以read ,write为例: 如果底层缓冲区没有数据

Python线程 适合I/O处理以及涉及阻塞操作的并发执行任务,不适合计算密集型

文章目录 为什么这种情况适合 I/O 和阻塞操作?1. I/O 操作和阻塞操作的特点:I/O 操作:阻塞操作: 2. GIL 对计算密集型任务的影响:计算密集型任务:GIL 的限制: 3. I/O 和阻塞操作的优势:I/O 操作的非 CPU 密集性:多线程的并发性: 具体示例:计算密集型任务:I/O 密集型任务: 总结: 全局解释器锁(Global Interpreter Lock