linux下的shell多线程用法,Linux: shell实现多线程, Forking / Multi-Threaded Processes | Bash...

按照shell语法,后一个前台命令必须等待前一个前台命令执行完毕才能进行,这就是所谓的单线程程序。如果两条命令之间有依赖性还好,否则后一条命令就白白浪费了等待的时间了。

网上查了一遍,shell并没有真正意义上的多进程。而最简单的节省时间,达到“多线程”效果的办法,是将前台命令变成后台进程,这样一来就可以跳过前台命令的限制了。

引用网上例子:

实例一:全前台进程:

#!/bin/bash

#filename:simple.sh

starttime=$(date +%s)

for ((i=0;i<5;i++));do

{

sleep 3;echo 1>>aa && endtime=$(date +%s) && echo "我是$i,运行了3秒,整个脚本执行了$(expr $endtime - $starttime)秒"

}

done

cat aa|wc -l

rm aa

运行测试:

# time sh simple.sh

我是0,运行了3秒,整个脚本执行了3秒

我是1,运行了3秒,整个脚本执行了6秒

我是2,运行了3秒,整个脚本执行了9秒

我是3,运行了3秒,整个脚本执行了12秒

我是4,运行了3秒,整个脚本执行了15秒

12

real    0m15.131s

user    0m0.046s

sys     0m0.079s

”我是X,运行了3秒“就规规矩矩的顺序输出了。

实例二:使用’&’+wait 实现“多进程”实现

#!/bin/bash

#filename:multithreading.sh

starttime=$(date +%s)

for ((i=0;i<5;i++));do

{

sleep 3;echo 1>>aa && endtime=$(date +%s) && echo "我是$i,运行了3秒,整个脚本执行了$(expr $endtime - $starttime)秒"

}&

done

wait

cat aa|wc -l

rm aa

运行测试:

# time sh  multithreading.sh

我是2,运行了3秒,整个脚本执行了3秒

我是3,运行了3秒,整个脚本执行了3秒

我是1,运行了3秒,整个脚本执行了3秒

我是4,运行了3秒,整个脚本执行了3秒

我是0,运行了3秒,整个脚本执行了3秒

5

real    0m3.105s

user    0m0.065s

sys     0m0.115s

运行很快,而且很不老实(顺序都乱了,大概是因为expr运算所花时间不同)

解析:这一个脚本的变化是在命令后面增加了&标记,意思是将进程扔到后台。在shell中,后台命令之间是不区分先来后到关系的。所以各后台子进程会抢夺资源进行运算。

wait命令:

wait  [n]

n 表示当前shell中某个执行的后台命令的pid,wait命令会等待该后台进程执行完毕才允许下一个shell语句执行;如果没指定则代表当前shell后台执行的语句,wait会等待到所有的后台程序执行完毕为止。

如果没有wait,后面的shell语句是不会等待后台进程的,一些对前面后台进程有依赖关系的命令执行就不正确了。例如wc命令就会提示aa不存在。

实例三:可控制后台进程数量的“多进程”shell

前一个shell也虽然达到“多进程”执行效果,但是并发执行的子进程数量却是不受控制的。

想想看,如果某一个作业需要执行1万次,那么到底是单进程实现还是像实例1那样多进程实现呢?前者,你慢慢等待吧;后者,祝你好运,希望你的计算机同时执行那么多进程不宕机,不卡死。

而实例3则可以实现可控制进程数量的多线程。

#!/bin/sh

function a_sub {

sleep 3;

echo 1>>aa && endtime=$(date +%s) && echo "我是$i,运行了3秒,整个脚本执行

了$(expr $endtime - $starttime)秒" && echo

}

starttime=$(date +%s)

export starttime

tmp_fifofile="/tmp/$.fifo"

echo $tmp_fifofile

mkfifo $tmp_fifofile

exec 6<>$tmp_fifofile

rm $tmp_fifofile

thread=3

for ((i=0;i

do

echo

done >&6

for ((i=0;i<10;i++))

do

read -u6

{

a_sub || {echo "a_sub is failed"}

echo >&6

} &

done

wait

exec 6>&-

wc -l aa

rm -f aa

exit 0

执行测试

# sh multi.2.sh  &

[1] 5360

sh multi.2.sh  &

[1] 6418

[root@bogon shell]#我是2,运行了3秒,整个脚本执行了3秒

我是0,运行了3秒,整个脚本执行了3秒

我是1,运行了3秒,整个脚本执行了3秒

我是5,运行了3秒,整个脚本执行了6秒

我是3,运行了3秒,整个脚本执行了6秒

我是4,运行了3秒,整个脚本执行了6秒

我是6,运行了3秒,整个脚本执行了9秒

我是8,运行了3秒,整个脚本执行了9秒

我是7,运行了3秒,整个脚本执行了9秒

我是9,运行了3秒,整个脚本执行了12秒

10 aa

在另一个ssh终端观察进程的运行

watch -d 'ps aux | grep multi.2.sh | grep -v grep'

Every 2.0s: ps aux | grep multi.2.sh | grep -v grep Fri May 11 04:18:51 2012

root 5360 0.1 0.9 106084 1272 pts/0 S 04:18 0:00 sh multi.2.sh

root 5400 0.0 0.3 106084 548 pts/0 S 04:18 0:00 sh multi.2.sh

root 5401 0.0 0.3 106084 548 pts/0 S 04:18 0:00 sh multi.2.sh

root 5404 0.0 0.3 106084 548 pts/0 S 04:18 0:00 sh multi.2.sh

可以看到同一时间只有4个进程(其中一个是shell本身(pid=5360)),子进程数量得到了控制。

或者:

#!/bin/bash

# 2006-7-12, by wwy

#———————————————————————————–

# 此例子说明了一种用wait、read命令模拟多线程的一种技巧

# 此技巧往往用于多主机检查,比如ssh登录、ping等等这种单进程比较慢而不耗费cpu的情况

# 还说明了多线程的控制

#———————————————————————————–

function a_sub { # 此处定义一个函数,作为一个线程(子进程)

sleep 3 # 线程的作用是sleep 3s

}

tmp_fifofile="/tmp/$$.fifo"

mkfifo $tmp_fifofile # 新建一个fifo类型的文件

exec 6<>$tmp_fifofile # 将fd6指向fifo类型

rm $tmp_fifofile

thread=15 # 此处定义线程数

for ((i=0;i

echo

done >&6 # 事实上就是在fd6中放置了$thread个回车符

for ((i=0;i<50;i++));do # 50次循环,可以理解为50个主机,或其他

read -u6

# 一个read -u6命令执行一次,就从fd6中减去一个回车符,然后向下执行,

# fd6中没有回车符的时候,就停在这了,从而实现了线程数量控制

{ # 此处子进程开始执行,被放到后台

a_sub && { # 此处可以用来判断子进程的逻辑

echo "a_sub is finished"

} || {

echo "sub error"

}

echo >&6 # 当进程结束以后,再向fd6中加上一个回车符,即补上了read -u6减去的那个

} &

done

wait # 等待所有的后台子进程结束

exec 6>&- # 关闭df6

exit 0

sleep 3s,线程数为15,一共循环50次,所以,此脚本一共的执行时间大约为12秒

即:

15×3=45, 所以 3 x 3s = 9s

(50-45=5)<15, 所以 1 x 3s = 3s

所以 9s + 3s = 12s

$ time ./multithread.sh >/dev/null

real 0m12.025s

user 0m0.020s

sys 0m0.064s

而当不使用多线程技巧的时候,执行时间为:50 x 3s = 150s。

此程序中的命令

mkfifo tmpfile

和linux中的命令

mknod tmpfile p

效果相同。区别是mkfifo为POSIX标准,因此推荐使用它。该命令创建了一个先入先出的管道文件,并为其分配文件标志符6。管道文件是进程之 间通信的一种方式,注意这一句很重要

exec 6<>$tmp_fifofile # 将fd6指向fifo类型

如果没有这句,在向文件$tmp_fifofile或者&6写入数据时,程序会被阻塞,直到有read读出了管道文件中的数据为止。而执行了上面这一句后就可以在程序运行期间不断向fifo类 型的文件写入数据而不会阻塞,并且数据会被保存下来以供read程序读出。

总结一下:

方案1:使用”&”使命令后台运行

在linux中,在命令的末尾加上&符号,则表示该命令将在后台执行,这样后面的命令不用等待前面的命令执行完就可以开始执行了。示例中的循环体内有多条命令,则可以以{}括起来,在大括号后面添加&符号。

#/bin/bash

all_num=10

a=$(date +%H%M%S)

for num in `seq 1 ${all_num}`

do

{

sleep 1

echo ${num}

} &

done

b=$(date +%H%M%S)

echo -e "startTime:\t$a"

echo -e "endTime:\t$b"

运行结果:

startTime: 194147

endTime: 194147

[j-tester@merger142 ~/bin/multiple_process]$ 1

2

3

4

5

6

7

8

9

10

通过结果可知,程序没有先打印数字,而是直接输出了开始和结束时间,然后显示出了命令提示符[j-tester@merger142 ~/bin/multiple_process]$(出现命令提示符表示脚本已运行完毕),然后才是数字的输出。这是因为循环体内的命令全部进入后台,所以均在sleep了1秒以后输出了数字。开始和结束时间相同,即循环体的执行时间不到1秒钟,这是由于循环体在后台执行,没有占用脚本主进程的时间。

方案2:命令后台运行+wait命令

解决上面的问题,只需要在上述循环体的done语句后面加上wait命令,该命令等待当前脚本进程下的子进程结束,再运行后面的语句。

#/bin/bash

all_num=10

a=$(date +%H%M%S)

for num in `seq 1 ${all_num}`

do

{

sleep 1

echo ${num}

} &

done

wait

b=$(date +%H%M%S)

echo -e "startTime:\t$a"

echo -e "endTime:\t$b"

运行结果:

1

2

3

4

5

6

7

9

8

10

startTime: 194221

endTime: 194222

但这样依然存在一个问题:

因为&使得所有循环体内的命令全部进入后台运行,那么倘若循环的次数很多,会使操作系统在瞬间创建出所有的子进程,这会非常消耗系统的资源。如果循环体内的命令又很消耗系统资源,则结果可想而知。

方案3:使用文件描述符控制并发数

最好的方法是并发的进程是可配置的。

#/bin/bash

all_num=10

thread_num=5

a=$(date +%H%M%S)

tempfifo="my_temp_fifo"

mkfifo ${tempfifo}

exec 6<>${tempfifo}

rm -f ${tempfifo}

for ((i=1;i<=${thread_num};i++))

do

{

echo

}

done >&6

for num in `seq 1 ${all_num}`

do

{

read -u6

{

sleep 1

echo ${num}

echo "" >&6

} &

}

done

wait

exec 6>&-

b=$(date +%H%M%S)

echo -e "startTime:\t$a"

echo -e "endTime:\t$b"

运行结果:

1

3

2

4

5

6

7

8

9

10

startTime: 195227

endTime: 195229

这个方案有时通不过 mkfifo ${tempfifo} ,那么就才用更简单的方法来控制:

for ARG in $*; do

command $ARG &

NPROC=$(($NPROC+1))

if [ "$NPROC" -ge 4 ]; then

wait

NPROC=0

fi

done

4 是最大线程,command 是你要执行的代码。

方案4:使用xargs -P控制并发数

xargs命令有一个-P参数,表示支持的最大进程数,默认为1。为0时表示尽可能地大,即方案2的效果。

#/bin/bash

all_num=10

thread_num=5

a=$(date +%H%M%S)

seq 1 ${all_num} | xargs -n 1 -I {} -P ${thread_num} sh -c "sleep 1;echo {}"

b=$(date +%H%M%S)

echo -e "startTime:\t$a"

echo -e "endTime:\t$b"

运行结果:

1

2

3

4

5

6

8

7

9

10

startTime: 195257

endTime: 195259

方案5:使用GNU parallel命令控制并发数

GNU parallel命令是非常强大的并行计算命令,使用-j参数控制其并发数量。

#/bin/bash

all_num=10

thread_num=6

a=$(date +%H%M%S)

parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10`

b=$(date +%H%M%S)

echo -e "startTime:\t$a"

echo -e "endTime:\t$b"

运行结果:

1

2

3

4

5

6

7

8

9

10

startTime: 195616

endTime: 195618

总结

“多线程”的好处不言而喻,虽然shell中并没有真正的多线程,但上述解决方案可以实现“多线程”的效果,重要的是,在实际编写脚本时应有这样的考虑和实现。

另外:

方案3、4、5虽然都可以控制并发数量,但方案3显然写起来太繁琐。

方案4和5都以非常简洁的形式完成了控制并发数的效果,但由于方案5的parallel命令非常强大,所以十分建议系统学习下。

方案3、4、5设置的并发数均为5,实际编写时可以将该值作为一个参数传入。

cbd5a61765aed016a56103b68dcc8d51.gif

相关

Related Posts

linux下的shell多线程用法,Linux: shell实现多线程, Forking / Multi-Threaded Processes | Bash..._第1张图片

Linux Shell脚本入门教程系列之(二)第一个shell脚本

本文是Linux Shell脚本系列教程的第(二)篇,更多shell教程请看:Linux Shell脚本系列教程 通过上一篇教程的学习,相信大家已经能够对shell建立起一个大体的印象了,接下来,我们通过一个最简单的脚本来继续深入对shell的学习。 新建shell脚本 新建一个文件,扩展名为sh(sh代表shell),或者其他任意名字,其实扩展名并不影响脚本执行,见名知意就好,这里用sh是为了便于分辨。 在你新建的文件中输入以下内容: #!/bin/bash echo…

linux下的shell多线程用法,Linux: shell实现多线程, Forking / Multi-Threaded Processes | Bash..._第2张图片

Linux Shell脚本入门教程系列之(一)Shell简介

本文是Linux Shell脚本系列教程的第(一)篇,更多shell教程请看:Linux Shell脚本系列教程 想要学习linux,shell知识必不可少,今天就给大家来简单介绍下shell的基本知识。 Shell简介 Shell自身是一个用C语言编写的程序,是用户来使用Unix或Linux的桥梁,用户的大部分工作都需要通过Shell来完成。只有熟练使用shell,才能熟练掌握linux。 可以说:Shell既是一种命令语言,又是一种程序设计语言。 作为命令语言,它可以交互式地解释和执行用户输入的命令;而作为程序设计语言,它可以定义各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。 Shell虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式来协调各个程序的运行。…

linux下的shell多线程用法,Linux: shell实现多线程, Forking / Multi-Threaded Processes | Bash..._第3张图片

Linux: Shell脚本备份MySQL数据库, Linux shell script for database backup

Shell脚本是我们写不同类型命令的一种脚本,这些命令在这一个文件中就可以执行。我们也可以逐一敲入命令手动执行。如果我们要使用shell脚本就必须在一开始把这些命令写到一个文本文件中,以后就可以随意反复运行这些命令了。 我首先要在本文带给你的是完整脚本。后面会对该脚本做说明。我假定你已经知道shell scripting、 mysqldump和crontab。 适用操作系统:任何Linux或UNIX。 主脚本(用于备份mysql数据库): 该Shell脚本可以自动备份数据库。只要复制粘贴本脚本到文本编辑器中,输入数据库用户名、密码以及数据库名即可。我备份数据库使用的是mysqlump 命令。后面会对每行脚本命令进行说明。 1. 分别建立目录“backups”和“history” mkdir…

你可能感兴趣的:(linux下的shell多线程用法,Linux: shell实现多线程, Forking / Multi-Threaded Processes | Bash...)