韦德国际1946官网Shell脚本模拟多线程效能分享,

2019-05-03 22:24 来源:未知

Shell 都以串行的方式自上而下执行命令,不适用需要大量作业的场景。
学习此篇shell脚本进程并发,能够大大提高工作效率~
通过wait 和 & 后台符号 可以实现并行,但无法控制进程数。

转载文章-原文地址http://findingcc.blog.51cto.com/1045158/287417很感谢大佬的分享,真的超详细!!

复制代码 代码如下:

{
    task
}&
done
wait

在网上看到shell模拟多线程的脚本,看了一下,估计对于新手还是很难理解,本文将进行更加详细的注解。

#!/bin/bash
function pinghost {
ping $1 -c 1 -w 10 |grep rtt|cut -d “/” -f6
}
tmp_fifofile=”/tmp/$.fifo”   # 脚本运行的当前进程ID号作为文件名
mkfifo $tmp_fifofile         # 新建一个随机fifo管道文件
exec 6<>$tmp_fifofile         # 定义文件描述符6指向这个fifo管道文件
rm $tmp_fifofile
thread=10
for ((i=0;i<$thread;i ));do   # for循环 往 fifo管道文件中写入10个空行
echo
done >&6
while read domain
do
read -u6                  # 从文件描述符6中读取行(实际指向fifo管道)
{
pinghost ${domain};      # 执行pinghost函数
echo >&6                      # 再次往fifo管道文件中写入一个空行。
}&                                  # 放到后台执行
done</home/miotour/ip.txt
wait                          #因为之前的进程都是后台执行,因此要有wait来等待所有的进程都执行完毕后才算整个脚本跑完。
exec 6>&-                #删除文件描述符6
exit 0

{} 将主执行程序变为一个块,使用&放入后台
wait 函数等待所有后台进程执行程序,否则继续执行后续命令直到整个脚本结束

其实这个脚本并没有实现多线程,shell也根本不可能实现多线程。

说明:{} 这部分语句被放入后台作为一个子进程执行,这部分几乎是同时完成的,当fifo中10个空行读完后 while循环
继续等待 read 中读取fifo数据,当后台的10个子进程后,按次序排队往fifo输入空行,这样fifo中又有了数据,for语句继续执行。

通过有名管道控制并发进程数

创建一个fifo文件, 作为进程池, 里面存放一定数目的"令牌".作业运行规则如下: 所有作业排队依次领取令牌; 每个作业运行前从进程池中领取一块令牌, 完成后再归还令牌; 当进程池中没有令牌时, 要运行的作业只能等待. 这样就能保证同时运行的作业数等于令牌数.

管道 = = 》有名管道、无名(匿名)管道
有名管道:mkfilo 创建一个管道文件
有名管道: “cat 1.file | grep ‘xxx’ ” “|” ==》创建一个无名管道,直接作为两个进程的数据通道

exec 自行定义,绑定文件操作符

系统默认三个文件操作符 0、1、2 = = 》 stdin、stdout、stderr
ls /proc/self/fd

模板

#!/bin/bash
trap "exec 6>&-;exec 6<&-;wxit 0" 2
#接受2 程序终止(interrupt)信号 "ctrl c" 后的操作。关闭fd6 
tmp_fifofile=/tmp/$$.fifo        //$$ 进程pid  
mkfifo $tmp_fifofile             //创建为进程pid的管道
                               //我常用$((RANDOM)),大概率的避免与已有文件重复
exec 6<>$tmp_fifofile           //以6为文件描述符fd打开管道 <>代表读写
rm $tmp_fifofile
thread=250                      //定义并发进程数量,上文的令牌数量
#在fd6中放入$thread 个空行作为令牌
for ((i=0; i<=$thread;i  ))
do
  echo
done >&6
for i in ``                              //可以省略,直接在{}括号内添加执行命令
do
    read -u6                             //read 读取行,领取令牌              
    {


    echo >& 6                            //归还令牌
}&                                       //{ }&放入后台
done
wait                                     //等待所有后台子进程结束
exec 6>&-                                //关闭fd6
exec 6<&-                                //关闭fd6

此脚本的作用无非是限制几乎同时放入后台执行的进程数量而已,从而达到在提高脚本执行效率的同时又不明显增加负载的作用。

您可能感兴趣的文章:

  • Shell多线程操作及线程数控制实例

结束

学术不精。欢迎评论一起讨论!~

附上一个自己写的使用并发,检查大批量站点的域名检测脚本
将待检查的脚本放入指定目录就行了~

#!/bin/bash

#创建今日目录
if [ ! -d "./$(date  %y-%m-%d)" ];then
    mkdir -p /script/$(date  %y-%m-%d)
fi
dir=/script/$(date  %y-%m-%d)

function global()
{
#第一次curl检测
tmp_fifofile=/tmp/$(($RANDOM00)).fifo
mkfifo $tmp_fifofile
exec 6<>$tmp_fifofile
rm $tmp_fifofile
thread=256
for ((i=0; i<=$thread;i  ))
do
   echo
done >&6

for ((i=0;i<=$thread;i  ))
do
    echo >&6
done

for i  in `cat /script/domain/$url`
do
   read -u6
   {
   code=$(curl -o /dev/null --retry 2 --connect-timeout 10 -s -w %{http_code} $i)
   echo "$code $i" >> $dir/$url-first.log
   echo >& 6
}&

done
wait
exec 6>&-
exec 6<&-
grep -v '200|301|302'  $dir/$url-first.log  |tail -n  2  |awk -F' ' '{print $2}' > $dir/$url-second.log
rm -f $dir/$url-first.log  
#第二次wget检测
tmp_fifofile=/tmp/$(($RANDOM00)).fifo
mkfifo $tmp_fifofile
exec 6<>$tmp_fifofile
rm $tmp_fifofile
thread=128
for ((i=0; i<=$thread;i  ))
do
   echo >&6
done

for i in `cat $dir/$url-second.log`
do
   read -u6
   {
    wget -T 10 --spider -t 2 $i &>/dev/null $i >> /dev/null
    if [ $? = 0 ];then
     echo $i >> /dev/null
    else
     echo $i >> $dir/$url-third.log
    fi
   echo >& 6
}&
done
wait
exec 6>&-
exec 6<&-
rm -f $dir/$url-second.log

#第三次curl检测
tmp_fifofile=/tmp/$(($RANDOM00)).fifo
mkfifo $tmp_fifofile
exec 6<>$tmp_fifofile
rm $tmp_fifofile
thread=128
for ((i=0; i<=$thread;i  ))
do
   echo >&6
done

for i  in `cat $dir/$url-third.log`
do
   read -u6
   {
   code=$(curl -o /dev/null --retry 2 --connect-timeout 10 -s -w %{http_code} $i)
   echo "$code $i" >> $dir/$url-fourth.log
   echo >& 6
}&

done
wait
exec 6>&-
grep -v '200|301|302'  $dir/$url-fourth.log  |tail -n  2   >> $dir/last.log
rm -f $dir/$url-third.log
rm -f $dir/$url-fourth.log

}

function last (){
grep -v '200|301|302' $dir/last.log |awk -F' ' '{print $2}' >> $dir/last2.log 
rm -f $dir/last.log
tmp_fifofile=/tmp/last.fifo
mkfifo $tmp_fifofile
exec 6<>$tmp_fifofile
rm $tmp_fifofile
thread=64
for ((i=0; i<=$thread;i  ))
do
   echo
done >&6

for ((i=0;i<=$thread;i  ))
do
    echo >&6
done


for i  in `cat $dir/last2.log`
do
   read -u6
   {
   code=$(curl -o /dev/null --retry 2 --connect-timeout 10 -s -w %{http_code} $i)
   echo "$code $i" >> $dir/last3.log
   echo >& 6
}&
done
wait
exec 6>&-
exec 6<&-
rm -f $dir/last2.log
echo "请手动复核以下域名:" > $dir/$(date  %H-00)domain.log
grep -v '200|301|302' $dir/last3.log >> $dir/$(date  %H-00)domain.log
rm -f $dir/last3.log
}


function main ()
{
tmp_fifofile=/tmp/main.fifo
mkfifo $tmp_fifofile
exec 8<>$tmp_fifofile
rm $tmp_fifofile
thread=2
for ((i=0; i<=$thread;i  ))
do
   echo
done >&8

for url in `ls -l /script/domain/ | tail -n  2 | awk -F' ' '{print $9}'`
do
  read -u8
{
  global $url
  echo >& 8
}&

done
wait
exec 8>&-
exec 8<&-

}

main
last
mail -s "检测结果来自xx服务器 :" xxxxxxxx@qq.com < $dir/$(date  %H-00)domain.log 

原始脚本如下:

#!/bin/bash

SEND_THREAD_NUM=13

tmp_fifofile="/tmp/$$.fifo"

mkfifo "$tmp_fifofile"

exec 6<>"$tmp_fifofile"

for ((i=0;i<$SEND_THREAD_NUM;i ));do

echo

done >&6

for i in `seq 1 100`;do

read -u6

{

echo $i

sleep 3

echo >&6

} &

pid=$!

echo $pid

done

wait

exec 6>&-

exit 0

以下为详细注解。

#设置线程数,在这里所谓的线程,其实就是几乎同时放入后台(使用&)执行的进程。

SEND_THREAD_NUM=13

# 脚本运行的当前进程ID号作为文件名,其实这样命名只是为了防止创建管道文件时与现有文件名重复,从而引起创建失败,别无他用。

tmp_fifofile="/tmp/$$.fifo"

#创建管道文件

mkfifo "$tmp_fifofile"

#把文件描述符6指向管道文件,文件描述符可以使用3-9任意一个即可(除了5),0、1、2、5已有定义,具体可参考关于文件描述符的文章。

EXEC 6<>"$tmp_fifofile"

#删除管道文件,其实删不删无所谓,看你自己了。

rm -f $tmp_fifofile

#使用一个for循环向管道中输入$SEND_THREAD_NUM个空行;>符号为输出重导向;&符号表示6为文件描述符,也就是说&6表示文件描述符6.

for ((i=0;i<$SEND_THREAD_NUM;i ));do

echo

done >&6

for i in `seq 1 100`;do                            # 100 次 for 循环开始,也就是说表示100次你的实际应用

read -u6                                    # 从文件描述符6(即管道)中读取行,每次读一行,由于是管道,读一行,管道中便少一行,每次只能读取一行,注意:如果读完了,便会挂起,直到管道中再次有可读的行。

{

echo $i                         # 打印i,这里代表整个脚本的应用部分,可以替换成你的实际应用。

sleep 3                         # 暂停3秒,这里是关键点,其实引入管道模拟多线程的关键就是为了这个暂停的3秒(实际上是微大于3秒的),让系统有个缓冲的时间,起到限制所谓并发的进程数量。

echo >&6                    # 再次往管道文件中写入一个空行,为了挂起的for循环能够继续执行。

} &    #注意这里要放到后台,也就是说echo $i这个你的应用有13个是几乎同时在后台执行的,为什么是13个呢?当for循环完13次后会挂起,因为管道已经空了,read -u6会暂时挂起,直到管道中有了空行。由于本commands block(即{}中的内容)是放在后台执行的,可以理想的看成这13次循环是同时执行的,大家同时sleep了3秒,而又同时向管道中输入了空行,所以管道在读空之后差不多3秒的时候又瞬时多了13次空行,使得read -u6能够继续读行,for循环得以继续执行。

pid=$!                                 #  $!代表在后台执行的最后一个进程。

echo $pid                            #打印最后一个进入后台的子进程id;打印的pid结果印证了非并发,非多线程。

done

wait   #等到后台的进程都执行完毕。

exec 6>&-                                    #删除文件描述符6

exit 0

TAG标签: 韦德娱乐1946
版权声明:本文由韦德娱乐1946_韦德娱乐1946网页版|韦德国际1946官网发布于韦德国际1946官网,转载请注明出处:韦德国际1946官网Shell脚本模拟多线程效能分享,