迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 >

如何在 Bash 脚本中使用多线程编程

作者:迹忆客 最近更新:2021/09/18 浏览次数:

开发人员一直对多线程编程很感兴趣,主要用来提高应用程序性能和优化资源的使用。本篇将向您介绍 Bash 多线程编程的基础知识。

什么是多线程编程

一张图胜过千言万语,当谈到 Bash 中单线程编程和多线程编程之间的区别时,使用图来解释要比文字方便的多:

sleep 1
sleep 1 & sleep 1

Bash 多线程编程sleep命令

我们的第一个多线程编程使用单行脚本再简单不过了; 在第一行中,我们使用 sleep 1 命令休眠一秒钟。 就用户而言,单个线程正在执行一秒钟的单个睡眠。

在第二行,我们有两个睡眠 1 秒的命令。我们通过使用&分隔符将它们连接起来,& 不仅作为两个sleep命令之间的分隔符,而且在 Bash 中指示第一个命令在后台线程中启动。

通常,我们会使用分号 ; 来终止命令。 这样做将执行命令,然后才继续执行分号后面列出的下一个命令。 例如,执行 sleep 1; sleep 1 只需要两秒钟多一点——第一个命令正好一秒钟,第二个命令一秒钟,并且两个命令中的每一个都需要少量的系统开销。

然而,除了使用分号终止命令之外,还可以使用 Bash 识别的其他命令终止符,如 &&&||。 && 语法与多线程编程完全无关,它只是这样做:只有在第一个命令成功时才继续执行第二个命令。 ||&&相反,仅当第一个命令失败时才执行第二个命令。

回到多线程编程,使用&作为我们的命令终止符将启动一个后台进程,执行它前面的命令。然后它立即继续在当前 shell 中执行下一个命令,同时让后台进程(线程)自行执行。

在命令的输出中,我们可以看到正在启动的后台进程(如 [1] 7136 所示,其中 7136 是刚刚启动的后台进程的进程 ID 或 PID,而 [1] 表示这是我们的第一个后台进程 ) 并随后被终止(如 [1]+ Done sleep 1 所示)。

现在让我们来证明一下我们是同时有效地运行两个sleep进程:

time sleep 1; echo 'done'
time $(sleep 1 & sleep 1); echo 'done'

Bash 多线程编程time下的sleep命令

在这里,我们在time下开始睡眠过程,我们可以看到在返回命令行提示符之前,单线程命令是如何运行1.003秒的。

然而,在第二个示例中,即使我们执行了两个睡眠周期(和进程),但它花费了大约相同的时间(1.005 秒),尽管不是连续的。 我们再次为第一个 sleep 命令使用后台进程,导致(半)并行执行,即多线程。

我们还在两个sleep命令周围使用了子shell包装器$(…),以便在time下将它们组合在一起。正如我们可以看到的,我们的输出显示是在1.005秒内执行完成,因此两个sleep 1命令肯定是同时运行的。有趣的是,总体处理时间(0.002秒)的增长非常小,这可以很容易地用启动子shell所需的时间和启动后台进程所需的时间来解释。


多线程进程和后台进程管理

在 Bash 中,多线程编程通常会涉及来自主单行脚本或完整 Bash 脚本的后台线程。 从本质上讲,可以将 Bash 中的多线程编程视为启动多个后台线程。 当人们开始使用多个线程进行编码时,很快就会清楚这些线程通常需要一些处理。 例如,假设我们在 Bash 脚本中启动五个并发的 sleep 周期(和进程);

rest.sh

#!/bin/bash

sleep 10 & 
sleep 600 & 
sleep 1200 & 
sleep 1800 & 
sleep 3600 &

Bash 多线程编程后台进程

当我们启动脚本时(在使用chmod +x rest.sh使其具有可执行权限后),我们看不到任何输出!即使我们执行 jobs 命令(显示任何正在进行的后台作业的命令),也没有输出。这是为什么?

原因是,用于启动此脚本的shell(即当前shell)与执行实际 sleep 命令或将其放在后台的 shell(也不是同一线程;从子shell的角度考虑,它们本身就是线程)不同。而是在执行./rest.sh时启动的(子)shell。

现在我们修改 rest.sh 脚本,在脚本中添加 jobs 命令。这将确保 jobs 在相关的(子)shell 内执行,与 sleep 周期(和进程)开始的位置相同。

rest.sh

#!/bin/bash

sleep 10 & 
sleep 600 & 
sleep 1200 & 
sleep 1800 & 
sleep 3600 &

jobs

Bash 多线程编程jobs命令

这一次,由于脚本末尾的jobs命令,我们可以看到正在启动的后台进程列表。我们还可以看到它们的PID(进程标识符)。在处理和管理后台进程时,这些PID非常重要。

获取后台进程标识符的另一种方法是在将程序/进程放入后台后立即查询它:

rest.sh

#!/bin/bash

sleep 10 & 
echo ${!}
sleep 600 & 
echo ${!}
sleep 1200 & 
echo ${!}
sleep 1800 & 
echo ${!}
sleep 3600 &
echo ${!}

Bash 多线程编程脚本查询后台进程

类似于我们的 jobs 命令(当我们重新启动我们的 rest.sh 脚本时,显示进程有了新的 PID),由于 Bash ${!} 变量被回显,我们现在几乎可以在脚本启动后立即看到五个 PID: sleep 进程被一个接一个地放入后台线程。

wait 命令

一旦我们开始了我们的后台进程,我们就没有别的事可做了,只能等待它们完成。但是,当每个后台进程正在执行一个复杂的子任务,并且我们需要主脚本(启动后台进程)在一个或多个后台进程终止时恢复执行时,我们需要额外的代码来处理这个问题。

现在让我们使用wait命令扩展脚本,来处理后台线程:

rest.sh

#!/bin/bash

sleep 10 & 
T1=${!}
sleep 600 & 
T2=${!}
sleep 1200 & 
T3=${!}
sleep 1800 & 
T4=${!}
sleep 3600 &
T5=${!}

echo "This script started 5 background threads which are currently executing with PID's ${T1}, ${T2}, ${T3}, ${T4}, ${T5}."
wait ${T1}
echo "Thread 1 (sleep 10) with PID ${T1} has finished!"
wait ${T2}
echo "Thread 2 (sleep 600) with PID ${T2} has finished!"

Bash 多线程编程 wait 命令

在这里,我们用两个wait命令扩展了我们的脚本,它们等待连接到第一个和第二个线程的PID终止。10秒后,我们的第一个线程就存在了,我们会收到同样的通知。这个脚本将一步一步地执行以下操作:几乎同时启动五个线程(尽管线程本身的启动仍然是顺序的,而不是并行的),其中五个 sleep 线程中的每一个都将并行执行。

然后,主脚本(按顺序)报告创建的线程,并随后等待第一个线程的进程ID终止。当这种情况发生时,它将依次报告第一个线程的完成情况,并开始等待第二个线程完成,等等。

当涉及到在Bash中并行运行多个线程(作为后台线程)时,使用Bash常用的用法 &${!}wait命令为我们提供了极大的灵活性。

除非注明转载,本站文章均为原创或翻译,欢迎转载,转载请以链接形式注明出处

本文地址:

迹忆客

专注技术分享,项目实战分享!

技术宅 乐于分享 7年编程经验
社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

热门文章

教程更新

热门标签