【Linux】深入 Linux 进程等待机制:阻塞与非阻塞的奥秘

🌈个人主页:Yui_

🌈Linux专栏:Linux

🌈C语言笔记专栏:C语言笔记

🌈数据结构专栏:数据结构

🌈C++专栏:C++

文章目录

  • [1. 为什么需要进行进程等待](#1. 为什么需要进行进程等待)
  • [2. 进程等待的方法](#2. 进程等待的方法)
    • [2.1 wait方法](#2.1 wait方法)
    • [2.2 waitpid方法](#2.2 waitpid方法)
    • [2.3 获取子进程status](#2.3 获取子进程status)
    • [2.4 进程的堵塞等待方式](#2.4 进程的堵塞等待方式)
    • [2.5 进程的非堵塞等待方法](#2.5 进程的非堵塞等待方法)
  • 3.解释堵塞与非堵塞

1. 为什么需要进行进程等待

进程等待是多进程编程中至关重要的一部分,主要原因是为了让父进程正确管理子进程生命周期并避免各种问题。

  • 进行资源回收,当子进程结束后,操作系统不会立即释放与该进程相关的所有资源,需要父进程来获取子进程的终止状态,并释放这些资源。
  • 避免僵尸进程,虽然子进程已经结束运行,但是它在进程中仍然回保留占位条目,需要父进程回收。
  • 获取子进程的退出状态,用来判断子进程是否成功执行完成任务

2. 进程等待的方法

2.1 wait方法

头文件

c 复制代码
#include <sys/types.h>
#include <sys/swit.h>

语法格式

c 复制代码
pid_t wait(int*status);

放回值:

成功返回被等待进程的pid,失败返回-1

参数:

输出型参数,获取子进程的退出状态,不甘心则可以设置为NULL

测试代码:

c 复制代码
#include <stdio.h>                                               
#include <unistd.h>                                              
#include <sys/types.h>                                           
#include <stdlib.h>                                             
#include <sys/wait.h>                                           
int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork error");
        exit(-1);
        //错误情况                                               
    }
    if (id == 0)
    {
        //child process                                          
        int cnt = 5;
        while (cnt--)
        {
            printf("i am a child process pid:%d\n", getpid());;
            sleep(1);
        }
        exit(0);//process exit                                   
    }
    sleep(10);//wait child process                               
    printf("i am a father pid:%d\n", getpid());
    pid_t ret = wait(NULL);
    if (ret > 0) printf("father wait:%d,success\n", ret);
    else ("father wait failed\n");
    sleep(10);
    return 0;
}

查看进程状态:

shell 复制代码
ubuntu@VM-20-9-ubuntu:~/processWait$ while :; do ps axj | head -1 && ps axj | grep process| grep -v grep; sleep 1; echo "#######################################"; done
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 Z+    1000   0:00 [process] <defunct>
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 Z+    1000   0:00 [process] <defunct>
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 Z+    1000   0:00 [process] <defunct>
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 Z+    1000   0:00 [process] <defunct>
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
 668279  668280  668279  651091 pts/0     668279 Z+    1000   0:00 [process] <defunct>
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
 651091  668279  668279  651091 pts/0     668279 S+    1000   0:00 ./process
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
#######################################
   PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND

查看上面的情况,我们可以发现,在中间有段时间子进程进入了僵尸状态,后来就被父进程给回收了。

2.2 waitpid方法

头文件:

c 复制代码
#include <sys/types.h>
#include <sys/swit.h>

语法格式:

c 复制代码
pid_t waitpid(pid_t pid,int* status,int options);

返回值:

当正常放回的时候waitpid返回搜集到的子进程进程ID
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0。
如果调用中出错,则返回-1,这时error会被设置成相对应的值以指示错误所在。

参数:

pid:
	pid=-1,等待任意一个子进程,与wait等效。
	pid>0,等待其进程ID与pid相等的子进程。
status:
	WIFEXITED(status):如果正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
	WEXISTATUS(status):如果WIFEXIED非0,提取子进程退出码。(查看进程退出码)
options:
	WNOHANG:如果pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待,如果正常结束,则返回该子进程的ID。
  • 如果子进程已经退出,调用wait/waitpid会立即返回,并且释放资源,获取子进程退出信息。
  • 如果任意时刻调用wait/waitpid,子进程存在且正常运行,则可能阻塞。
  • 如果不存在该进程,则立即出错放回。

2.3 获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 如果不传递NULL,操作系统会会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整型来看,应该当作位图来看待。

测试代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
	pid_t id = fork();
	if(id<0)
	{
		perror("fork fail");
		exit(1);
	}
	if(id == 0)
	{
		//child
		printf("i am a child pid:%d\n",getpid());
		sleep(20);
		exit(10);
	}
	else
	{
		//father
		int status;
		int ret = wait(&status);
		if(ret>0&&(status&0x7F)==0)
		{
			//normal exit
			printf("child exit code:%d\n",(status>>8)&0xFF);

		}
		else if(ret>0)
		{
			printf("sig code:%d\n",status&0x7F);
		}


	}


	return 0;
}

正常退出会打印:child exit code:10

运行过程中被kill掉:sig code:9

2.4 进程的堵塞等待方式

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork fail");
		exit(1);
	}
	else if(id == 0)
	{
		//child
		printf("i am a child,pid is:%d\n",getpid());
		sleep(5);
		exit(257);
	}
	else
	{	
		//father
		int status = 0;
		pid_t ret = waitpid(-1,&status,0);//阻塞等待
		printf("This is test for wait\n");
		if(WIFEXITED(status)&&ret == id)
		{
			printf("wait child 5s success,child return code is:%d\n",WEXITSTATUS(status));

		}
		else
		{
			printf("wait child failed,return\n");
			return 1;
		}


	}
	return 0;
}

执行过程:

因为阻塞等待的缘故,在子进程结束前父进程都不会执行下一条语句。会一直卡在pid_t ret = waitpid(-1,&status,0);者句语句。

执行结果:

i am a child,pid is:701810
This is test for wait
wait child 5s success,child return code is:1

提问:为什么会放回1呢

回答:

在上面的图中,我们得知了但程序正常退出后,其退出状态存储在低8位中,而257的二进制位为0001 0000 0001,会被截断为1

2.5 进程的非堵塞等待方法

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork fail");
		exit(1);
	}
	else if(id == 0)
	{
		//child
		printf("i am a child,pid is:%d\n",getpid());
		sleep(5);
		exit(1);
	}
	else
	{	
		//father
		int status = 0;
		pid_t ret = 0;
		do
		{
			ret = waitpid(-1,&status,WNOHANG);//非阻塞等待
			if(ret == 0){
				printf("child is running\n");
			}
			sleep(1);
		}while(ret == 0);
		if(WIFEXITED(status)&&ret == id)
		{
			printf("wait child 5s success,child return code is:%d\n",WEXITSTATUS(status));

		}
		else
		{
			printf("wait child failed,return\n");
			return 1;
		}


	}
	return 0;
}

执行结果:

child is running
i am a child,pid is:711095
child is running
child is running
child is running
child is running
wait child 5s success,child return code is:1

从这两段代码大家肯定可以分的清楚堵塞和非堵塞的区别了吧。就是还不理解,下面我在来一个例子相信大家一定就没有问题了。

3.解释堵塞与非堵塞

  • 阻塞场景:打电话等朋友接听
    • 你拨打朋友的电话,直到朋友接通之前你什么都做不了。这就像阻塞调用,你必须等着事情完成。
  • 非阻塞场景:发消息等待回复
    • 你给朋友发了个消息,等他们回你。你不用一直盯着手机看,而是可以去做别的事情,等收到消息后再查看。这就像非阻塞调用,你不需要等着完成才能做其他事情。

就是如此,感谢观看本篇文文章,提前感谢大家的点赞与收藏~

相关推荐
秋名山小桃子1 分钟前
Kunlun 2280服务器(ARM)Raid卡磁盘盘符漂移问题解决
运维·服务器
与君共勉121382 分钟前
Nginx 负载均衡的实现
运维·服务器·nginx·负载均衡
岑梓铭9 分钟前
(CentOs系统虚拟机)Standalone模式下安装部署“基于Python编写”的Spark框架
linux·python·spark·centos
努力学习的小廉9 分钟前
深入了解Linux —— make和makefile自动化构建工具
linux·服务器·自动化
MZWeiei13 分钟前
Zookeeper基本命令解析
大数据·linux·运维·服务器·zookeeper
7yewh28 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
Arenaschi32 分钟前
在Tomcat中部署应用时,如何通过域名访问而不加端口号
运维·服务器
小张认为的测试32 分钟前
Linux性能监控命令_nmon 安装与使用以及生成分析Excel图表
linux·服务器·测试工具·自动化·php·excel·压力测试
waicsdn_haha39 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
打鱼又晒网40 分钟前
linux网络套接字 | 深度解析守护进程 | 实现tcp服务守护进程化
linux·网络协议·计算机网络·tcp