其实在 shell 脚本中差没有多线程这个概念,此脚本结合管道文件和文件描述符巧妙的实现了多线程的功能。阅读本文前请先查阅管道文件和文件描述符相关的基础知识。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash  
  
# 设置线程数,在这里所谓的线程,其实就是几乎同时放入后台(使用&)执行的进程。  
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_pifofile  
  
# 使用一个for循环向管道中输入$SEND_THREAD_NUM个空行;>符号为输出重导向;&符号表示6为文件描述符,也就是说&6表示文件描述符6.  
for ((i=0;i<$SEND_THREAD_NUM;i++));do  
    echo  
done >&6  
  
# 100 次 for 循环开始,也就是说表示100次你的实际应用  
for i in `seq 1 100`;do  
  
    # 从文件描述符6(即管道)中读取行,  
    # 每次读一行,由于是管道,读一行,管道中便少一行,  
    # 每次只能读取一行,注意:如果读完了,便会挂起,直到管道中再次有可读的行。  
    read -u6                                                                          
  
    {  
        # 打印i,这里代表整个脚本的应用部分,可以替换成你的实际应用。  
        echo $i  
  
        # 暂停3秒,这里是关键点,其实引入管道模拟多线程的关键就是为了这个暂停的3秒(实际上是微大于3秒的),  
        # 让系统有个缓冲的时间,起到限制所谓并发的进程数量。                                                 
        sleep 3  
  
        # 再次往管道文件中写入一个空行,为了挂起的for循环能够继续执行。                                                   
        echo >&6  
  
    # 注意这里要放到后台,也就是说echo $i这个你的应用有13个是几乎同时在后台执行的,  
    # 为什么是13个呢?当for循环完13次后会挂起,因为管道已经空了,  
    # read -u6会暂时挂起,直到管道中有了空行。由于本commands block(即{}中的内容)是放在后台执行的,  
    # 可以理想的看成这13次循环是同时执行的,大家同时sleep了3秒,而又同时向管道中输入了空行,  
    # 所以管道在读空之后差不多3秒的时候又瞬时多了13次空行,使得read -u6能够继续读行,for循环得以继续执行。  
    } &  
  
    #  $!代表在后台执行的最后一个进程。  
    pid=$!  
  
    #打印最后一个进入后台的子进程id;打印的pid结果印证了非并发,非多线程。  
    echo $pid  
done  
  
#等到后台的进程都执行完毕。  
wait  
  
#删除文件描述符6  
exec 6>&-  
  
exit 0  

转自:http://findingcc.blog.51cto.com/1045158/287417