如何在 Bash 脚本中使用多线程编程
开发人员一直对多线程编程很感兴趣,主要用来提高应用程序性能和优化资源的使用。本篇将向您介绍 Bash 多线程编程的基础知识。
什么是多线程编程
?
一张图胜过千言万语,当谈到 Bash 中单线程编程和多线程编程之间的区别时,使用图来解释要比文字方便的多:
sleep 1
sleep 1 & sleep 1
我们的第一个多线程编程使用单行脚本再简单不过了; 在第一行中,我们使用 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'
在这里,我们在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 &
当我们启动脚本时(在使用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
这一次,由于脚本末尾的jobs命令,我们可以看到正在启动的后台进程列表。我们还可以看到它们的PID(进程标识符)。在处理和管理后台进程时,这些PID非常重要。
获取后台进程标识符的另一种方法是在将程序/进程放入后台后立即查询它:
rest.sh
#!/bin/bash
sleep 10 &
echo${!}
sleep 600 &
echo${!}
sleep 1200 &
echo${!}
sleep 1800 &
echo${!}
sleep 3600 &
echo${!}
类似于我们的 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!"
在这里,我们用两个wait
命令扩展了我们的脚本,它们等待连接到第一个和第二个线程的PID终止。10秒后,我们的第一个线程就存在了,我们会收到同样的通知。这个脚本将一步一步地执行以下操作:几乎同时启动五个线程(尽管线程本身的启动仍然是顺序的,而不是并行的),其中五个 sleep 线程中的每一个都将并行执行。
然后,主脚本(按顺序)报告创建的线程,并随后等待第一个线程的进程ID终止。当这种情况发生时,它将依次报告第一个线程的完成情况,并开始等待第二个线程完成,等等。
当涉及到在Bash中并行运行多个线程(作为后台线程)时,使用Bash常用的用法 &
、${!}
和wait
命令为我们提供了极大的灵活性。
本文转载自:迹忆客(https://www.jiyik.com)
以上是 如何在 Bash 脚本中使用多线程编程 的全部内容, 来源链接: utcz.com/z/290225.html