非阻塞轮询

目录

  • 前言
  • [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 即可。

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

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

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


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

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

感谢各位观看!

相关推荐
cominglately2 小时前
centos单机部署seata
linux·运维·centos
魏 无羡2 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse2 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
木子Linux3 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.8243 小时前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu
鹏大师运维3 小时前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs
watermelonoops3 小时前
Windows安装Ubuntu,Deepin三系统启动问题(XXX has invalid signature 您需要先加载内核)
linux·运维·ubuntu·deepin
滴水之功4 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible4 小时前
How to run Flutter on an Embedded Device
linux
YRr YRr5 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu