shell后台并发执行实践

shell如何在后台执行

1.nohup命令
通常我们都是远程登录linux终端,而当我们退出终端时在之前终端运行的程序都会终止,有时候先想要退出终端也要程序继续执行这时nohup就登场了。nohup命令可以将程序以忽略挂起信号的方式运行起来,被运行的程序的输出信息将不会显示到终端。
nohup command > myout.file 2>&1 &

2.&后台执行
在命令后面加 & 可以让程序在后台执行
command &

3.Ctrl + z
当一个程序正在执行并且占用当前终端时我们同时按下 Ctrl + z ,这样就会把正在执行的前台程序放到后台挂起。

并发执行

1.正常执行

#!/bin/bash
Njob=10    #任务总数
for ((i=0; i<$Njob; i++)); do
{
	echo  "progress $i is sleeping for 1 seconds zzz…"
	sleep  1
}
done
echo -e "time-consuming: $SECONDS seconds"    #显示脚本执行耗时

执行结果

progress 0 is sleeping for 1 seconds zzz…
progress 1 is sleeping for 1 seconds zzz…
progress 2 is sleeping for 1 seconds zzz…
progress 3 is sleeping for 1 seconds zzz…
progress 4 is sleeping for 1 seconds zzz…
progress 5 is sleeping for 1 seconds zzz…
progress 6 is sleeping for 1 seconds zzz…
progress 7 is sleeping for 1 seconds zzz…
progress 8 is sleeping for 1 seconds zzz…
progress 9 is sleeping for 1 seconds zzz…
-e time-consuming: 10 seconds

2.并发后台执行

#!/bin/bash
Njob=10
for ((i=0; i<$Njob; i++)); do
    echo  "progress $i is sleeping for 3 seconds zzz…"
    sleep  3 &       #循环内容放到后台执行
done
wait      #等待循环结束再执行wait后面的内容
echo -e "time-consuming: $SECONDS seconds"    #显示脚本执行耗时

执行结果

progress 0 is sleeping for 3 seconds zzz…
progress 1 is sleeping for 3 seconds zzz…
progress 2 is sleeping for 3 seconds zzz…
progress 3 is sleeping for 3 seconds zzz…
progress 4 is sleeping for 3 seconds zzz…
progress 5 is sleeping for 3 seconds zzz…
progress 6 is sleeping for 3 seconds zzz…
progress 7 is sleeping for 3 seconds zzz…
progress 8 is sleeping for 3 seconds zzz…
progress 9 is sleeping for 3 seconds zzz…
-e time-consuming: 3 seconds

这种方式从功能上实现了使用shell脚本并行执行多个循环进程,但是它缺乏控制机制。

for设置了Njob次循环,同一时间Linux就触发Njob个进程一起执行。假设for里面执行的是scp,在没有pam_limits和cgroup限制的情况下,很有可能同一时刻过多的scp任务会耗尽系统的磁盘IO、连接数、带宽等资源,导致正常的业务受到影响。

一个应对办法是在for循环里面再嵌套一层循环,这样同一时间,系统最多只会执行内嵌循环限制值的个数的进程。不过还有一个问题,for后面的wait命令以循环中最慢的进程结束为结束(水桶效应)。如果嵌套循环中有某一个进程执行过程较慢,那么整体这一轮内嵌循环的执行时间就等于这个“慢”进程的执行时间,整体下来脚本的执行效率还是受到影响的。

分批并行的方式并发执行

#!/bin/bash
NQ=3
num=5
for ((i=0; i<$NQ; i++)); do
     for ((j=0; j<$num; j++)); do
         echo  "progress $i is sleeping for 3 seconds zzz…"
        sleep 3 &
     done
     wait
 done
#等待循环结束再执行wait后面的内容
echo -e "time-consuming: $SECONDS    seconds"    #显示脚本执行耗时

执行结果

 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 0 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 1 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
 progress 2 is sleeping for 3 seconds zzz…
-e time-consuming: 9    seconds

3.使用模拟队列来控制进程数量

要控制后台同一时刻的进程数量,需要在原有循环的基础上增加管理机制。

一个方法是以for循环的子进程PID做为队列元素,模拟一个限定最大进程数的队列(只是一个长度固定的数组,并不是真实的队列)。队列的初始长度为0,循环每创建一个进程,就让队列长度+1。当队列长度到达设置的并发进程限制数之后,每隔一段时间检查队列,如果队列长度还是等于限制值,那么不做操作,继续轮询;如果检测到有并发进程执行结束了,那么队列长度-1,轮询检测到队列长度小于限制值后,会启动下一个待执行的进程,直至所有等待执行的并发进程全部执行完。

#!/bin/bash
Njob=15 #任务总数
Nproc=5 #最大并发进程数

function PushQue {      #将PID值追加到队列中
           Que="$Que $1"
           Nrun=$(($Nrun+1))
}

function GenQue {       #更新队列信息,先清空队列信息,然后检索生成新的队列信息
           OldQue=$Que
           Que=""; Nrun=0
           for PID in $OldQue; do
                 if [[ -d /proc/$PID ]]; then
                        PushQue $PID
                 fi
           done
}

function ChkQue {       #检查队列信息,如果有已经结束了的进程的PID,那么更新队列信息
           OldQue=$Que
           for PID in $OldQue; do
                 if [[ ! -d /proc/$PID ]];   then
                 GenQue; break
                 fi
           done
}

for ((i=1; i<=$Njob; i++)); do
           echo "progress $i is sleeping for 3 seconds zzz…"
           sleep 3 &
           PID=$!
           PushQue $PID
           while [[ $Nrun -ge $Nproc ]]; do          # 如果Nrun大于Nproc,就一直ChkQue
                 ChkQue
                 sleep 0.1
           done
done
wait
echo -e "time-consuming: $SECONDS   seconds"    #显示脚本执行耗时

执行结果

progress 1 is sleeping for 3 seconds zzz…
progress 2 is sleeping for 3 seconds zzz…
progress 3 is sleeping for 3 seconds zzz…
progress 4 is sleeping for 3 seconds zzz…
progress 5 is sleeping for 3 seconds zzz…
progress 6 is sleeping for 3 seconds zzz…
progress 7 is sleeping for 3 seconds zzz…
progress 8 is sleeping for 3 seconds zzz…
progress 9 is sleeping for 3 seconds zzz…
progress 10 is sleeping for 3 seconds zzz…
progress 11 is sleeping for 3 seconds zzz…
progress 12 is sleeping for 3 seconds zzz…
progress 13 is sleeping for 3 seconds zzz…
progress 14 is sleeping for 3 seconds zzz…
progress 15 is sleeping for 3 seconds zzz…
-e time-consuming: 3   seconds

这种使用队列模型管理进程的方式在控制了后台进程数量的情况下,还能避免个别“慢”进程影响整体耗时的问题:

4.使用fifo管道特性来控制进程数量

管道是内核中的一个单向的数据通道,同时也是一个数据队列。具有一个读取端与一个写入端,每一端对应着一个文件描述符。
命名管道即FIFO文件,通过命名管道可以在不相关的进程之间交换数据。FIFO有路径名与之相关联,以一种特殊设备文件形式存在于文件系统中。

FIFO有两种用途:

• FIFO由shell使用以便数据从一条管道线传输到另一条,为此无需创建临时文件,常见的操作cat file|grep keyword就是这种使用方式;
• FIFO用于客户进程-服务器进程程序中,已在客户进程与服务器进程之间传送数据,下面的例子将使用这种方式。

根据FIFO文件的读规则(参考http://www.cnblogs.com/yxmx/articles/1599187.html),如果有进程写打开FIFO,且当前FIFO内没有数据,对于设置了阻塞标志的读操作来说,将一直阻塞状态。

利用这一特性可以实现一个令牌机制。设置一个行数等于限定最大进程数Nproc的fifo文件,在for循环中设置创建一个进程时先read一次fifo文件,进程结束时再write一次fifo文件。如果当前子进程数达到限定最大进程数Nproc,则fifo文件为空,后续执行的并发进程被读fifo命令阻塞,循环内容被没有触发,直至有某一个并发进程执行结果并做写操作(相当于将令牌还给池子)。

需要注意的是,当并发数较大时,多个并发进程即使在使用sleep相同秒数模拟时,也会存在进程调度的顺序问题,因而并不是按启动顺序结束的,可能会后启动的进程先结束。

#!/bin/bash

Njob=15 #任务总数

Nproc=5 #最大并发进程数

mkfifo ./fifo.$$ && exec   9<>  ./fifo.$$     #通过文件描述符777访问fifo文件

for ((i=0; i<$Nproc; i++)); do  #向fifo文件先填充等于Nproc值的行数
  echo  "init time add $i" >&9
done
for ((i=0; i<$Njob; i++)); do
{
  read  -u  9             #从fifo文件读一行
  echo  "progress $i is sleeping for 3 seconds zzz…"
  sleep  3
  echo  "real time add $(($i+$Nproc))"  1>&9 #sleep完成后,向fifo文件重新写入一行
} &
done
wait
echo -e "time-consuming: $SECONDS seconds"
rm -f ./fifo.$$

执行结果

progress 0 is sleeping for 3 seconds zzz…
progress 1 is sleeping for 3 seconds zzz…
progress 2 is sleeping for 3 seconds zzz…
progress 3 is sleeping for 3 seconds zzz…
progress 4 is sleeping for 3 seconds zzz…
progress 5 is sleeping for 3 seconds zzz…
progress 6 is sleeping for 3 seconds zzz…
progress 8 is sleeping for 3 seconds zzz…
progress 12 is sleeping for 3 seconds zzz…
progress 13 is sleeping for 3 seconds zzz…
progress 9 is sleeping for 3 seconds zzz…
progress 11 is sleeping for 3 seconds zzz…
progress 14 is sleeping for 3 seconds zzz…
progress 10 is sleeping for 3 seconds zzz…
progress 7 is sleeping for 3 seconds zzz…
-e time-consuming: 10 seconds

原文地址:
Shell脚本实现并发多进程
Shell脚本并发执行

OKR 填写指南

一、OKR概述
OKR是一个目标管理工具。其中O指Objective,是团队或个人的工作目标;KR指Key Result,是一系列可以衡量的关键结果,用来判断Objective是否达成。在作业帮,OKR的制定和共享,是公司、团队和个人制定任务,对齐目标,协调和集中精力的重要手段。

二、OKR执行要点
工作目标设置应该激进,要使自己和团队感受到压力。
关键结果要容易打分衡量,不要模棱两可。
除保密事项外,OKR尽可能的公开,方便互相了解在忙些什么工作。
写好OKR,要求严格的聚焦,只写最重要的目标,不罗列堆砌。
OKR并不是绩效考核工具,但应该是自我检测工作成果的重要工具。
OKR不是一个共享的工作清单或者to-do list,而是一个管理精力,自我规划的重要工具。
没有经过沟通和对齐的OKR,相当于没有写,OKR的对齐方包括你的上级、同事和你承接需求(提出需求)的协同方。

三、设定激进、聚焦的目标

在每个OKR周期中,公司从CEO到全体员工都会制定自己的OKR,通常是三四个Objectives,每个Objective有3个左右的Key Results。这些OKR既有从上到下目标分解而来的,也有从基层收集的各种意见演化而来,所以OKR的制定是一个循环迭代和讨论的过程。

我们希望每一个人都制定激进的目标,这些目标看上去刚好处于 “这个周期完不成” 的边缘。事实证明,制定高目标,有助于我们取得优于普通水准的成果。 每个高目标都需要全情投入。所以OKR制定的另一方面,是严格地聚焦到重点方向,不贪多。但在选定的重点方向上,力求保证目标达成。

一些设定Objective的窍门:

选三五个目标即可,宁缺勿滥。太多目标,工作容易失去焦点,团队也会疲于应付。
描述最终状态,比如“上线xx功能”,“获得10%的市场份额”。
常规动作不要写进OKR,例如“继续推进”、“保持行业地位”。如果确是长期重要工作,要思考如何拆解成合理的周期OKR,而不是不经思考地写“继续做”。
团队leader是团队的大脑,更要注意排优先级和授权。

四、可衡量的Key Result

关键结果(Key Result,或简称KR)是用来评价目标是否达成的。考虑到这个功能,对关键结果最大的要求就是容易衡量,且直接支持目标的达成。

一些设定Key Result的窍门:

每个目标定3个左右KR即可,不多刻意多写。
注意,KR是用来衡量Objective的达成程度的,问一下自己“这个KR和相应的Objective有直接的支撑关系吗”。
要描述产出,而不是动作。产出是指类似于“发表一篇论文”这样的表述,而动作是指“进行研究并撰写论文”这样的表述。所以当一个KR中出现 “参与”、“分析”、“辅助”这样的词汇,或者描述过程的动词特别多时,可能就有问题了。
用客观、外部可观测和不模糊的表述。最好是你的同事也能够准确地对你的KR进行打分,这说明KR足够清晰。
五、常见错误
执行不好的OKR,不但对公司、团队和个人没有帮助,反而可能造成团队安于现状,甚至方向混淆、内耗。所以我们要避免常见的OKR执行错误,比如下面这些:

堆砌不重要的目标
OKR常常被写得很长、很多,很难用两三句阐述其中的重点。往往这样的OKR包含大量的常规工作。常规工作不是不可以写,而是要判断,这项工作需要我和团队付出额外的努力才能达成吗。因为OKR要求激进,“担心有可能完不成”,相当多的常规目标是达不到这个要求的。这里有两点需要澄清:一、OKR不是工作量的衡量工具,并且我们应当主要关注OKR对业务带来的实际效果,所以不必在OKR中堆砌工作量;二、避免堆砌,要求每个人有判断力,自觉抛弃低优先级的目标和低价值的目标,而将精力和资源放到高优先级目标上。可以问自己几个问题,“这件事不做有什么影响吗”,“这件事做了有实际的业务收益吗”。

只写一个单词
写OKR时容易偷懒,只写一个单词,比如“收入” “DAU” “xx项目”。这样的写法,既没有说明最终要达到的状态,也很难客观打分。同时在沟通和对齐的时候,其他同事也很难看懂。类似的,“其他项目”、“重点项目”也是不可取的Objective,因为项目真的重要话,应该明确说出来要达到什么目标;而归为“其他”的目标,通常可能并不重要。

给自己留余量
如果历史上所有的OKR都轻松达成,可能定目标的时候就不够有雄心。

六、沟通、对齐和进度更新
沟通和对齐有几个重要的作用。首先是在重要的方向上,配合团队和上下级形成合力,而不要往不同方向使力。其次,OKR的制定是一个迭代过程,从草稿到定稿,需要方方面面的意见输入。再次,要了解配合团队定OKR时的激进程度,判断如果配合团队的OKR只能完成0.5分,会不会影响自己的工作。另一方面,应该鼓励经常更新OKR进度,既是对自己的提醒鞭策,又是向同事同步信息的好方式。