shell子进程管理

简介

        在我们平时写代码过程中,可能经常会遇到串行执行速度慢 ,串行无法执行多个任务,这时便需要使用子进程同时执行。使用父进程创建子进程时,子进程会复制父进程的内存、文件描述符和其他相关信息。当然,子进程可以独立运行,并可以执行不同的操作。父进程和子进程是平行运行的,它们可以同时执行不同的任务。这篇文章的目的是为了让初学者了解如何通过shell实现多进程,多进程之间如何管理。

                

目录

1. 如何实现多进程

1.1. 后台命令的实现

1.2. 子进程的实现

1.3. 总结

2. 管理子进程

2.1.wait(等待子进程 )

2.2. trap(中断子进程)

2.3. 子进程随主进程退出

2.4. 总结


                

1. 如何实现多进程

在学习管理子进程之前,我们需要先了解2个基本的知识点

  1. 后台进程如何实现?
  2. 子进程如何实现?

1.1. 后台命令的实现

对于Linux来说,我们在某条命令后面加上 & 符号,就表示一个后台进程。比如 sleep 10 &

这里可以看到,我们在一条命令后面加了 & 符号,Linux会执行将其推入到后台执行,并显示PID,ps命令查询到该进程确实在运行中。

注意:这里有个坑,如果我们在某条Linux命令或某个shell脚本后面加入 & 符号,系统确实会推入后台执行,但如果我们退出了这个窗口,那么进程也将会自动退出。

上述的例子可以明显的看到,在窗口1执行后台命令时,退出当前窗口,使用窗口2查询,实际上是查询不到的,因为系统已经自动退出了。

避免这种情况的方法也很简单,在命令前方加入 nohup 命令即可

  • nohup 命令用于在后台运行程序,并且不受终端关闭或退出的影响。
  • nohup 是 “no hangup” 的缩写,它允许你在关闭终端后继续运行程序。

使用 nohup 后,即使当前终端关闭也不会影响到后台进程,并且后台进程输出所有正常或异常的结果会直接打印到当前路径下的 nohup.out 文件中。

                

1.2. 子进程的实现

通过上述《后台命令的实现》,小伙伴们理解了如何实现后台进程,而实现子进程的方式实际上也是使用同样的方法,例如:

#!/bin/bash
echo "我是子进程 1" &
echo "我是子进程 2" &
echo "我是子进程 3" &

我们在shell中对某条命令加上了 & 符号,那么系统将会把这几个命令当作子进程执行

                

上述的方法看不出实际的应用场景,我们来试试循环呢

#!/bin/bash
# 子进程1
for i in {1..3};doecho "我是子进程 ${i}"sleep 1
done &# 子进程2
for i in {10..13};doecho "我是子进程 ${i}"sleep 1
done &# 子进程3
for i in {20..23};doecho "我是子进程 ${i}"sleep 1
done &

使用for循环同时执行不同的命令,如下图(可以看到三个循环是同时执行的)

使用ps查询发现子进程的PPID都指向了最高等级进程1。这是由于主进程并不会随着子进程的运行而等待,这对我们管理起来是非常麻烦的。

解决的办法是在执行子进程下面加入 wait 命令,表示等待上面所有子进程执行完成后再继续执行下一步。

#!/bin/bash
# 子进程1
for i in {1..3};doecho "我是子进程 ${i}"sleep 1
done &# 子进程2
for i in {10..13};doecho "我是子进程 ${i}"sleep 1
done &# 子进程3
for i in {20..23};doecho "我是子进程 ${i}"sleep 1
done &
wait

这次可以很清晰的看到子进程指向的父ID是主进程

                

for 循环只是一个例子,在我们真正场景中用的最多的还是函数。下述举一个简单的例子:

#!/bin/bash
func1(){for i in {1..3};doecho "我是子进程 ${i}"sleep 1done}
func2(){for i in {10..13};doecho "我是子进程 ${i}"sleep 1done}# 将函数推入后台执行
func1 &
func2 &
wait    # 等待子进程运行完成后再退出

                

1.3. 总结

我们想要实现一个脚本,并且该脚本需要进行多个任务处理,参考目录《1.2. 子进程的实现》;而我们运行这个脚本时,参考目录《1.1. 后台命令的实现》。总结起来就是:

  1. 使用函数封装代码,在需要指定某个函数为子进程时,后面加上 & 符号;
  2. 当脚本运行时间长,那么需要使用 nohup + & 实现后台运行,保证程序正常运行。

                

2. 管理子进程

2.1.wait(等待子进程 )

wait 命令用于等待所有运行的子进程执行完毕,使用格式如下

wait [jobspec]
  • jobspec:表示进程ID,如果不指定则等待所有子进程。

                

案例一:编写两个子进程,使用wait指定等待其中一个进程

#!/bin/bash
func1(){sleep 3echo "我是子进程1, 运行3秒"}
func2(){sleep 10echo "我是子进程2, 运行10秒"}# 将函数推入后台执行
func1 &
func1_pid=$!    #获取上面子进程的PID
func2 &wait ${func1_pid}
echo -e "主进程运行完成, 退出!"

从脚本中,我们编写了2个子进程函数:函数1等待3s后打印,函数2等待10s后打印。在将函数1推入后台后获取该子进程的PID,而后使用 wait 指定等待该进程,对函数2不做等待。所以在执行时,子进程1执行3s后完成,主进程也随之执行完成;再等待7s后子进程2运行结束。

                

案例二:不指定wait,等待全部子进程

#!/bin/bash
func1(){sleep 3echo "我是子进程1, 运行3秒"}
func2(){sleep 10echo "我是子进程2, 运行10秒"}# 将函数推入后台执行
func1 &
func2 &wait    # 等待全部子进程
echo -e "主进程运行完成, 退出!"

从结果来看,与案例一明显不同的是,主进程是等待了所以的子进程才自动退出。

                

案例三:wait 的位置处于多个子进程中间

#!/bin/bash
func1(){sleep 3echo "我是子进程1, 运行3秒"}
func2(){sleep 10echo "我是子进程2, 运行10秒"}func1 &
wait
echo "=====等待子进程1运行完成!====="func2 &
wait
echo "=====等待子进程2运行完成!====="

我们将 wait 放在指定子进程的后面,在 wait 后面再放一个子进程,它的运行流程是从上往下依次执行。

                

2.2. trap(中断子进程)

命令如下(选择其一即可)

trap 'trap - EXIT; kill -s HUP -- -$$' EXIT
trap 'kill -s HUP -- -$$' EXIT
  • trap - EXIT:这部分的作用是取消当前进程对EXIT信号的附加处理,以防止进程在接收到EXIT信号时再次触发。
  • kill -s HUP -- -$$:这部分的作用是向进程组发送SIGHUP信号(通常是用来通知终端关闭的信号),-$$表示向当前进程的进程组发送信号。

                

trap 命令没有固定的位置规则,放子进程前面或后面都可以

#!/bin/bash
func1(){for i in {1..3};doecho "我是子进程1, 执行:${i}"sleep 1done}
func2(){for i in {10..20};doecho "我是子进程2, 执行:${i}"sleep 1done}#trap 'trap - EXIT; kill -s HUP -- -$$' EXIT
func1 &
func2 &
trap 'kill -s HUP -- -$$' EXITsleep 2
echo -e "主进程运行完成, 退出!"

脚本中,子进程1的运行时间是3s,子进程2的运行时间是10s,主进程的运行时间是2s。设置退出信号后,主进程运行2s后退出,相关子进程也随之退出。

注意:这里的退出要么是主进程正常运行结束,要么是 Ctrl + C 退出才能使子进程也随之退出。如果中途主进程被 kill 掉,那么 trap 信号也随之消失,所以主进程被 kill 后,子进程无法随主进程而退出,只能等子进程运行结束后自动退出。

                

2.3. 子进程随主进程退出

【案例一】

在脚本的最开始读取主进程PID,在子进程中使用 while 判断该进程是否存在,如果不存在则结束循环。这种方式相比于 trap 命令,即使主进程被 kill 也不会影响子进程正常退出。

#!/bin/bash# 定义当前脚本的PID
SCR_PID=$$func1(){while [ -d /proc/${SCR_PID} ];do    # 判断主进程PID是否存在echo "我是子进程1"sleep 1done}
func2(){while [ -d /proc/${SCR_PID} ];do    # 判断主进程PID是否存在echo "我是子进程2"sleep 1done}func1 &
func2 &sleep 2
echo -e "主进程运行完成, 退出!"

                

【案例二】

如果我们希望主进程在退出前结束子进程,可以让子进程判断修改为判断某个文件是否存在

#!/bin/bash# 定义用于判断的文件路径
check_file="./.check.txt"func1(){while [ ! -f ${check_file} ];do    # 如果该文件不存在则一直运行echo "我是子进程1"sleep 1done}
func2(){while [ ! -f ${check_file} ];do    # 如果该文件不存在则一直运行echo "我是子进程2"sleep 1done}func1 &
func2 &sleep 2
touch ${check_file}    # 创建一个文件
echo -e "主进程运行完成, 退出!"

                

【案例三】

如果我们希望主进程退出后,子进程需要做其他事,那么在子进程的循环下继续编写代码

#!/bin/bash# 定义用于判断的文件路径
check_file="./.check.txt"func1(){while [ ! -f ${check_file} ];doecho "我是子进程1"sleep 1doneecho "子进程1的循环结束,继续执行xxx"    # 继续执行下一步}
func2(){while [ ! -f ${check_file} ];doecho "我是子进程2"sleep 1done}func1 &
func2 &sleep 2
touch ${check_file}
echo -e "主进程运行完成, 退出!"

                

2.4. 总结

        管理子进程 wait 命令是必要的,它的作用是等待指定的某个子进程结束或等待全部子进程结束后才能继续执行下一步。如果主进程退出后,我们希望子进程也一同退出,可以使用 trap 命令或者目录《2.3. 子进程随主进程退出》的方法,当然,如果主进程被 kill 的话,trap就无效了,所以这里更推荐2.3的方法。

                

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web前端-JavaScript(js表达式)

文章目录 JavaScript基础第01天1.编程语言概述1.1 编程1.2 计算机语言1.2.1 机器语言1.2.2 汇编语言1.2.3 高级语言 1.4 翻译器 2.计算机基础2.1 计算机组成2.2 数据存储2.3 数据存储单位2.4 程序运行 3.初始JavaScript3.1 JavaScript 是什么3.2 JavaScript的作用3.3 HTML/CSS/…

医疗智能化革命:AI技术引领医疗领域的创新进程

一、“AI”医疗的崛起 随着人工智能(AI)技术的崛起,"AI"医疗正在以惊人的速度改变着医疗行业的面貌。AI作为一种强大的工具,正在为医疗领域带来前所未有的创新和突破。它不仅在医学影像诊断、病理学分析和基因组学研究等…

tomcat错误

Error running Tomcat8: Address localhost:1099 is already in use window环境,打开cmd netstat -ano | findstr :1099发现对应PID为24732 结束PID taskkill /PID 24732 /F

R语言【rgbif】——occ_search对待字符长度大于1500的WKT的特殊处理真的有必要吗?

一句话结论:只要有网有流量,直接用长WKT传递给参数【geometry】、参数【limit】配合参数【start】获取所有记录。 当我在阅读 【rgbif】 给出的用户手册时,注意到 【occ_search】 强调了 参数 【geometry】使用的wkt格式字符串长度。 文中如…

配置Nginx解决跨域问题

Nginx 中将前端请求中的所有以 “/apiUrl” 开头的路径代理到 http://192.12.200.101:9813 例如: /apiUrl/login > http://192.12.200.101:9813/login 配置nginx环境 进入Nginx 的配置文件编辑界面: sudo nano /etc/nginx/conf.d/default.conf开始编辑 defaul…

计算机网络(1):开始

计算机网络(1):开始 计算机网络在信息时代中的作用 21世纪的一些重要特征就是数字化、网络化和信息化,是一个以网络为核心的信息时代。要实现信息化就必须依靠完善的网络,因为网络可以非常迅速地传递信息。因此网络现…

STM32/STM8资源节约主义设计方式

STM32/STM8资源节约主义设计方式 在小资源芯片进行代码设计时,如STM32C0系列,STM8系列,因为官方库本身要包含各种场景应用特征的支持,所以会有一些冗余的代码占用更多FLASH空间。当需要实现资源占用最简化设计方式时,…

AAAI中稿心得

很幸运我们的一篇工作中稿了AAAI2024,题目是 Self-Prompt Mechanism for Few-Shot Image Recognition. 很高兴能在研二的上学期中稿一篇a会保底,也是我中稿的第一篇工作,成为我申请博士的资本。最重要的是,让枯燥无味的科研&#…

3.3【窗口】窗口的几何形状(二,窗口属性)

写在前面 应用程序使用窗口来显示内容。一些属性决定了窗口及其内容的大小和位置。其他属性决定了窗口内容的外观和解释。 了解窗口属性引用的两个坐标系非常重要。如果你知道你正在使用的坐标系,那么为你的窗口属性选择设置值会容易得多,并且会更有意义。 一,显示相关属…

12.16~12.17图的存储方式(邻接矩阵,邻接表),相应定义与构建,PTA特性(要初始化),BFS抓牛,判断题

图的存储方式 邻接矩阵 #include<iostream> using namespace std; #define maxn; struct tu {int juzhen[maxn][maxn];//行为起点&#xff0c;列为终点&#xff0c;即第一个为起点&#xff0c;第二个为终点//确定一个点的出度&#xff0c;就固定行&#xff0c;即第一个…

读取小数部分

1.题目描述 2.题目分析 //假设字符串为 char arr[] "123.4500"; 1. 找到小数点位置和末尾位置 代码如下&#xff1a; char* start strchr(arr, .);//找到小数点位置char* end start strlen(start) - 1;//找到末尾位置 如果有不知道strchr()用法的同学&#xf…

力扣刷题记录(10)LeetCode:51、37

51. N 皇后 应为各个皇后之间不能同行&#xff0c;所以一行只能有一个皇后。我们可以遍历每一行的各个位置&#xff0c;判断该位置是否可以放置皇后&#xff0c;一行放置一个。当一个皇后的位置已经在这一行确定了&#xff0c;我们就可以遍历下一行来确定下一个皇后在下一行的位…