(4.14)Fd和命名管道实现shell并发控制
原创
©著作权归作者所有:来自51CTO博客作者喜欢打篮球的普通人的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
- 1.shell并发控制之描述符
- 2.shell并发控制之管道
- 3.并发控制样例
- 4.别人的并发
1.shell并发控制之描述符
- File Descriptors(FD,文件描述符)或者文件句柄
进程使用文件描述符来管理打开的文件
ls /proc/$$/fd
0 1 2 3 4
0:标准输入
1:标准输出
2:标准错误输出
touch /file1
exec 6 <> /file1##手动打开了一个文件描述符,给他一个6描述符
- 因为文件句柄没有释放,就算删除/file1文件,但是文件描述符还在 ,所以可以copy出来,但是前后两个file1文件的inode编号不一样了!
当一个文件FD未释放,删除源文件也不会影响FD
虽然cp复原了,但是文件/file1的inode发生了改变,还是会显示deleted状态 - exec关闭一个文件(释放一个文件句柄)
exec 6<&-##关闭一个文件描述符,释放掉当前进程的文件句柄6
2.shell并发控制之管道
- 管道是一个文件
- 匿名管道:你放到里面的内容,被别人拿走就没了;在一个终端(进程)玩
- 命名管道:先进先出,内容读完就没了;两个终端(进程)去玩
命名管道,就是有名字的管道,eg: /tmp/fifo1
mkfifo /tmp/fifo1
file /tmp/fifo1
ll /dev/ > /tmp/fifo1
接着查看管道中的内容
grep 'sda' /tmp/fifo1
3.并发控制样例
#ping01
#!/usr/bin/bash
thread=5#进程数量
tmp_fifofile=/tmp/$$.fifo
mkfifo $tmp_fifofile
exec 8<> $tmp_fifofile
rm $tmp_fifofile ##描述符8还存在,是否删除tmp_fifofile对fd=8没有影响
for i in `seq $thread`
do
##这里用>>追加是没有意义的,因为这是管道,不是常规文件,所以这里不可能会覆盖前面的内容的,后面
##增加的内容对前面没有影响
echo >&8 ##&8指的就是文件描述符8,单个echo就是回车。echo "111" > &8
##该文件tmp_fifofile里面有5个回车
done
for i in {2..254}
do
##read --help,-u跟文件描述符
##读一个回车,就循环,循环到第6次的时候,就跳出read了,因为没得读了
##read会将管道中的5个空格都读完,读完才跳出read,但是每次读完一个空格又给管道塞一个空格
read -u 8
{
ip=192.168.122.$i
ping -c1 -W1 $ip &>/dev/null ## -W1表示1秒超时
if [ $? -eq 0 ];then
echo "$ip is up"
else
echo "$ip is down"
fi
echo >&8##上面的事做完了,就还回去。不要都借完了,再还回去,保证管道的输入有5个数据输入
}&
done
wait ##等待所有的后台进程执行结束,如果不加这里,有些ping执行不成功,就会执行echo "finish"
exec 8>&-
echo "finish"
左图中的横杠表示5个回车,read 从fd=8中去读,读完为止
4.别人的并发
#!/bin/bash
start=`date +%s`
for i in $(seq 1 5); do
echo test
sleep 2
done
end=`date +%s`
time=$(($end - $start))
echo "time: $time"
# sh test1.sh
test
test
test
test
test
time: 10
- shell中实现多进程实际上就是将多个任务放到后台中执行而已
(1)如果可以一次执行多个任务的,把它放到后台即可:
(2)说下改动,首先我把for循环中的代码用{}包为一个块,然后增加&符号使其后台运行,之后增加wait指令,该指令会等待所有后台进程结束,如果不加wait,程序直接往下走,最终打出的time将会是0
#!/bin/bash
start=`date +%s`
for i in $(seq 1 5); do
{
echo test
sleep 2
}&
done
wait
end=`date +%s`
time=$(($end - $start))
# sh test2.sh
test
test
test
test
test
time: 2
#!/bin/bash
##首先创建一个管道文件tm1,然后使用exec命令将该文件的输入输出绑定到5号文件描述符,而后删除该管道文件,
这里删除的只是该文件的Inode,实际文件已由内核open函数打开,这里删除并不会影响程序执行,
当程序执行完后,文件描述符会被系统自动回收。
mkfifo tm1
exec 5<>tm1
rm -f tm1
##通过一个for循环向该文件描述符写入10个空行,这个10其实就是我们定义的后台进程数量,
这里需要注意的是,管道文件的读取是以行为单位的。
for ((i=1;i<=10;i++)); do
echo >&5
done
这里假定我后台有100个任务,而我们在后台保证只有10个进程在同时运行
read -u5 的作用是,读取5号文件描述符中的一行,就是读取一个空行
在减少文件描述符的一个空行之后,在后台执行一次任务,而任务在执行完成以后,会向文件描述符中再写入一个空行,
这是为什么呢,因为如果我们写入空行的话,当后台放入了10个任务之后,由于没有可读取的空行,read -u5就会被阻塞住!
for ((j=1;j<=100;j++)); do
read -u5
{
echo test$j
sleep 2
echo >&5
}&
done
wait
exec 5>&-
exec 5<&-
我们生成做绑定时 可以用 exec 5<>tm1 来实现,
但关闭时,必须分开来写:> 读的绑定,< 标识写的绑定, <> 则标识 对文件描述符5的所有操作等同于对管道文件tm1的操作。
打开文件作为输入,并分配文件描述符
$exec 3<datafile
打开文件作为输出,并分配文件描述符
$exec 4>datafile