shell子进程管理

news2024/11/26 18:53:47

简介

        在我们平时写代码过程中,可能经常会遇到串行执行速度慢 ,串行无法执行多个任务,这时便需要使用子进程同时执行。使用父进程创建子进程时,子进程会复制父进程的内存、文件描述符和其他相关信息。当然,子进程可以独立运行,并可以执行不同的操作。父进程和子进程是平行运行的,它们可以同时执行不同的任务。这篇文章的目的是为了让初学者了解如何通过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};do
        echo "我是子进程 ${i}"
        sleep 1
done &

# 子进程2
for i in {10..13};do
        echo "我是子进程 ${i}"
        sleep 1
done &

# 子进程3
for i in {20..23};do
        echo "我是子进程 ${i}"
        sleep 1
done &

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

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

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

#!/bin/bash
# 子进程1
for i in {1..3};do
        echo "我是子进程 ${i}"
        sleep 1
done &

# 子进程2
for i in {10..13};do
        echo "我是子进程 ${i}"
        sleep 1
done &

# 子进程3
for i in {20..23};do
        echo "我是子进程 ${i}"
        sleep 1
done &
wait

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

                

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

#!/bin/bash
func1(){
        for i in {1..3};do
                echo "我是子进程 ${i}"
                sleep 1
        done
        }
func2(){
        for i in {10..13};do
                echo "我是子进程 ${i}"
                sleep 1
        done
        }

# 将函数推入后台执行
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 3
        echo "我是子进程1, 运行3秒"
        }
func2(){
        sleep 10
        echo "我是子进程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 3
        echo "我是子进程1, 运行3秒"
        }
func2(){
        sleep 10
        echo "我是子进程2, 运行10秒"
        }

# 将函数推入后台执行
func1 &
func2 &

wait    # 等待全部子进程
echo -e "主进程运行完成, 退出!"

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

                

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

#!/bin/bash
func1(){
        sleep 3
        echo "我是子进程1, 运行3秒"
        }
func2(){
        sleep 10
        echo "我是子进程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};do
                echo "我是子进程1, 执行:${i}"
                sleep 1
        done
        }
func2(){
        for i in {10..20};do
                echo "我是子进程2, 执行:${i}"
                sleep 1
        done
        }

#trap 'trap - EXIT; kill -s HUP -- -$$' EXIT
func1 &
func2 &
trap 'kill -s HUP -- -$$' EXIT

sleep 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 1
        done
        }
func2(){
        while [ -d /proc/${SCR_PID} ];do    # 判断主进程PID是否存在
                echo "我是子进程2"
                sleep 1
        done
        }

func1 &
func2 &

sleep 2
echo -e "主进程运行完成, 退出!"

                

【案例二】

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

#!/bin/bash

# 定义用于判断的文件路径
check_file="./.check.txt"

func1(){
        while [ ! -f ${check_file} ];do    # 如果该文件不存在则一直运行
                echo "我是子进程1"
                sleep 1
        done
        }
func2(){
        while [ ! -f ${check_file} ];do    # 如果该文件不存在则一直运行
                echo "我是子进程2"
                sleep 1
        done
        }

func1 &
func2 &

sleep 2
touch ${check_file}    # 创建一个文件
echo -e "主进程运行完成, 退出!"

                

【案例三】

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

#!/bin/bash

# 定义用于判断的文件路径
check_file="./.check.txt"

func1(){
        while [ ! -f ${check_file} ];do
                echo "我是子进程1"
                sleep 1
        done
        echo "子进程1的循环结束,继续执行xxx"    # 继续执行下一步
        }
func2(){
        while [ ! -f ${check_file} ];do
                echo "我是子进程2"
                sleep 1
        done
        }

func1 &
func2 &

sleep 2
touch ${check_file}
echo -e "主进程运行完成, 退出!"

                

2.4. 总结

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

                

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

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

相关文章

2023年全球运维大会(GOPS深圳站)-核心PPT资料下载

一、峰会简介 1、大会背景与概述 全球运维大会(GOPS)是运维领域最具影响力的国际盛会,每年都会汇聚世界各地的运维专家、企业领袖、技术爱好者,共同探讨运维技术的最新发展、最佳实践以及面临的挑战。2023年GOPS深圳站作为该系列…

亚马逊云科技re:Invent推出生成式AI技术堆栈及关键服务和工具

亚马逊云科技于29日推出“生成式AI技术堆栈”后,又在30日的re:Invent 2023大会上宣布了一系列支持这一全新堆栈的关键服务和工具。 亚马逊云科技数据和人工智能副总裁Swami Sivasubramanian在主题演讲中,将生成式人工智能与“超新星爆炸”进行了比较&am…

HttpRunner接口自动化测试框架

简介 HttpRunner是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。 项目地址:GitHub - httprunner/httprunner: HttpRunner 是一个开源的 API/UI…

Apache SeaTunne简介

Apache SeaTunne简介 文章目录 1.Apache SeaTunne是什么?1.1[官网](https://seatunnel.apache.org/)1.2 项目地址 2.架构3.特性3.1 丰富且可扩展的连接器和插件机制3.2 支持分布式快照算法以确保数据一致性3.3 支持流、批数据处理,支持全量、增量和实时数…

Web前端-HTML(常用标签)

文章目录 1. HTML常用标签1.1 排版标签1)标题标签h (熟记)2)段落标签p ( 熟记)3)水平线标签hr(认识)4)换行标签br (熟记)5)div 和 span标签(重点)6)排版标签总结 1.2 标签属性1.3 图像标签img (重点)1.4 链…

新算法!!! TSOA-CNN-LSTM-Attention凌日优化卷积、长短期记忆网络融合注意力机制的多变量回归预测程序,数据由Excel导入,直接运行

适用平台:Matlab2023版及以上 凌日优化算法(Transit Search Optimization Algorithm,TSOA)是2022年8月提出的一种新颖的元启发式算法,当一颗行星经过其恒星前方时,会导致恒星的亮度微弱地下降,…

分布式事务 | 2PC与3PC 详解

分布式事务 2PC 2PC ,两阶段提交,将事务的提交过程分成资源准备和资源提交两个阶段,并且由事务协调者来协调所有事务参与者,如果准备阶段所有事务参与者都预留资源成功,则进行第二阶段的资源提交,否则事务…

本章主要介绍Spring Framework中用来处理URI的多种方式

1.使用 UriComponentsBuilder 构建URi 话不多说 直接上代码 UriComponents uriComponents UriComponentsBuilder.fromUriString("https://example.com/hotels/{hotel}").queryParam("q", "{q}").encode().build();URI uri uriComponents.exp…

【Gradle】创建第一个项目

文章目录 1. 前提2. 创建项目并初始化1)创建项目2)初始化项目 3. 介绍生成的文件结构4. 执行5. 包的作成 (非必须)6. 推送(非必须) 本节将继 Gradle 之初体验 安装之后,创建第一个 Hello World…

Oracle(2-18)Export and Import Utilities

文章目录 一、基础知识1. Export &Import Utilities2、Exp/lmp Utility Overview Exp/mp实用程序概述3、Before Your Use Of Exp/lmp 在您使用Exp/lmp之前4、Methods to invoke Exp/lmp 调用Exp/lmp的方法5、Import Utility for Recovery 用于恢复的导入实用程序 二、基础操…

Mac如何安装stable diffusion

今天跟大家一起在Mac电脑上安装下stable diffusion,在midjourney等模型收费的情况下如何用自己的电脑算力用上免费的画图大模型呢?来吧一起实操起来 一、安装homebrew 官网地址:Homebrew — The Missing Package Manager for macOS (or Lin…

【科研论文】检索证明、科技查新、查收查引(附教育部、科技部查新工作站名单)

文章目录 1、什么是科技查新 & 查收查引2、科技查新 & 查收查引有什么用3、如何办理科技查新 & 查收查引4、教育部科技查新工作站5、科技部认定的查新机构名单 1、什么是科技查新 & 查收查引 科技查新是国家科技部为避免科研课题重复立项和客观正确地判别科研…

Android开发——组合函数、注解与连接Android设备

1、JetPack Compose、组合函数与注解和文本修改 1、JetPack Compose:Jetpack Compose 是由 Google 推出的用于构建 Android 用户界面的现代化工具包。它是一个声明式的 UI 工具包,用于简化 Android 应用程序的用户界面设计和开发。Jetpack Compose 采用…

并发编程中常见的设计模式

文章目录 一、 终止线程的设计模式1. 简介2. Tow-phase Termination(两阶段终止模式)—优雅的停止线程 二、避免共享的设计模式1. 简介2. Immutability模式—想破坏也破坏不了3. Copy-on-Write模式4. Thread-Specific Storage模式—没有共享就没有伤害 三…

跟着官网学 Vue - 插槽

Vue 插槽是一种强大的组件通信方式。 插槽内容与出口 在 Vue 中&#xff0c;插槽是一种让父组件向子组件传递内容的方式。子组件使用 <slot> 元素作为插槽出口&#xff0c;父组件可以通过插槽内容填充这些空白区域。 示例&#xff1a; <!-- MyButton.vue --> &…

代码随想Day39 | 62.不同路径、63. 不同路径 II

62.不同路径 每次向右或者向下走两个选择&#xff0c;定义dp数组dp[i][j] 为到达索引ij的路径和&#xff0c;状态转移公式为 dp[i][j]dp[i-1][j]dp[i][j-1]&#xff0c;初始状态的第一行和第一列为1&#xff0c;从左上到右下开始遍历即可。详细代码如下&#xff1a; class Sol…

BM61 矩阵最长递增路径

题目 矩阵最长递增路径 给定一个 n 行 m 列矩阵 matrix &#xff0c;矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径&#xff0c;使这条路径上的元素是递增的。并输出这条最长路径的长度。 这个路径必须满足以下条件&#xff1a; 1. 对于每个单元格&#xff0c;你…

Flink系列之:监控Checkpoint

Flink系列之&#xff1a;监控Checkpoint 一、概览二、概览&#xff08;Overview&#xff09;选项卡三、历史记录&#xff08;History&#xff09;选项卡四、历史记录数量配置五、摘要信息&#xff08;Summary&#xff09;选项卡六、配置信息&#xff08;Configuration&#xff…

【Linux】在vim中批量注释与批量取消注释

在vim编辑器中&#xff0c;批量注释和取消注释的操作可以通过进入V-BLOCK模式、选择要注释或取消注释的内容、输入注释符号或选中已有的注释符号和按键完成。这些操作可以大大提高代码或文本的编写和修改效率&#xff0c;是vim编辑器中常用的操作之一。 1.在vim中批量注释的步…