shell并发遍历目录并对文件进行处理

最近有需要对目录进行遍历,并对目录中的文件进行处理,发现网上没有找到比较适合的shell并发遍历的脚本,看来就只能自己写了。。。
首先想到的代码就是

#! /bin/bash
# 这里可以是耗时比较久的任务
function do_something() {
	echo "$(date "+%Y%m%d%H%M%S"): $1"
}
function traverse_dir(){
	local vPath=$1
	for vfile in `ls ${vPath}`       #注意此处这是两个反引号,表示运行系统命令
    do
        if [ -d "${vPath}/${vfile}" ];then  #注意此处之间一定要加上空格,否则会报错
            traverse_dir "${vPath}/${vfile}"
        else
            do_something "${vPath}/${vfile}"   #在此处处理文件即可
        fi
    done
}   

function main() {
	local vPath=$1
    local vStart=$(date +"%s")
    echo "start time: ${vStart}"
    local vEnd
	traverse_dir "${vPath}"
    vEnd=$(date +"%s")
    echo "start time: ${vStart} end time: ${vEnd}"
    echo "total time: $((vEnd-vStart)) seconds"

}

main "$@"

分析

这段代码虽然可以按照预期完成任务,但是如果do_something中的任务耗时比较久的话就会出现,整个遍历过程串行执行下来耗时非常的久。
这个时候有个2.0版本,就是将do_something "${vPath}/${vfile}"替换为do_something "${vPath}/${vfile}" &让耗时的任务在后台执行。

function traverse_dir(){
	local vPath=$1
	for vfile in `ls ${vPath}`
    do
        if [ -d "${vPath}/${vfile}" ];then
            traverse_dir "${vPath}/${vfile}"
        else
            do_something "${vPath}/${vfile}" &
        fi
    done
}  

那么假设目录规模很大想进一步提升遍历的速度呢?3.0版本出来了,给traverse_dir "${vPath}/${vfile}"也放到后台执行traverse_dir "${vPath}/${vfile}" &

function traverse_dir(){
	local vPath=$1
	for vfile in `ls ${vPath}`
    do
        if [ -d "${vPath}/${vfile}" ];then
            traverse_dir "${vPath}/${vfile}" &
        else
            do_something "${vPath}/${vfile}" &
        fi
    done
}

目录有多大,咱的进程就敢开多少,这个时候2.0和3.0的问题就暴露出来了——当目录非常多,以及目录下的非目录文件非常多的时候,脚本会由于开启的进程过多出现内存不足的报错,并且还会大量的占用主机的资源,导致整个主机都非常的卡,最终适得其反,处理速度反倒不如单进程。
此时是否存在一种方式能够控制并发度,使得遍历和处理速度不至于过慢,又不至于导致主机资源耗尽?
有没有觉得这个模型和生产者消费者很相似,哈哈是的没错,这就是一个典型的生产者消费者模型。
经过在其他博主那里取经——shell队列实现线程并发控制,最终咱们迎来了最终版本

最终版本

首先需要我们掌握几个知识。

有名管道

linux中的有名管道是一个类似队列的先进先出的结构,当从中拿取一份数据后,管道中的数据便会少一份,当从空的管道中拿数据时,相应的进程便会阻塞住。利用这个特性可以实现我们的并发控制

exec命令

有名管道可以使用mkfifo xxx的方式进行创建,为了使得我们的程序漂亮,并且各个shell进程都能独占自己的管道,我们需要了解exec命令。
通过exec fdNumber<>xxxxx 的方式我们可以将文件描述符和有名管道联系到一起,并且fdNumber这个文件描述符还具有有名管道没有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符
现在万事俱备只欠东风,看代码:

#!/bin/bash

readonly TMP_VISITOR="/tmp/visitor"
readonly TMP_DoSomething="/tmp/dosomething"

function create_fifo() {
    local vCpuNums=$(cat /proc/cpuinfo| grep "processor"| wc -l)
    local vVistorNums=$((2*vCpuNums))  # 二倍的CPU核心数进行遍历
    local vDoSomethingNums=$((4*vCpuNums))  # 四倍的CPU核心数处理耗时较长的任务
    # visitor
    mkfifo ${TMP_VISITOR}
    exec 3<>${TMP_VISITOR}
    rm -f ${TMP_VISITOR}
    for((i=0;i<${vVistorNums};++i)); do
        echo >&3
    done

    # migrater
    mkfifo ${TMP_DoSomething}
    exec 4<>${TMP_DoSomething}
    rm -f ${TMP_DoSomething}
    for((i=0;i<${vDoSomethingNums};++i)); do
        echo >&4
    done

}

function close_fifo() {
    exec 3<&-
    exec 3>&-

    exec 4<&-
    exec 4>&-
}

function do_something() {
    local vFileName=$1
    read -u4
    {
        echo "$(date "+%Y%m%d%H%M%S"): ${vFileName}"
        echo >&4
    }&
}


function traverse_dir() {
    local vPath=$1
    local vFlag=$2
    local vFile
    local vRc
    for vFile in `ls ${vPath}`
    do
        if [ -d "${vPath}/${vfile}" ];then
        	# read超时意味着遍历队列已经满了,这个时候应当用自身进程去执行traverse_dir
	        # 由于使用的是自身进程,没有消耗队列中的数量,因此传"false",在traverse_dir
	        # 执行结束后不用执行echo >&3
            read -t 1 -u 3
	        vRc=$?
	        if [[ ${vRc} -eq 0 ]]; then
	            traverse_dir "${vPath}/${vFile}" "true" &
	        else
	            traverse_dir "${vPath}/${vFile}" "false"
	        fi
        else
            do_something "${vPath}/${vFile}"
        fi
    done
    
    if [[ ${vFlag} == "true" ]]; then
        echo >&3
    fi
    wait
}

function main(){
    create_fifo
    local vPath="$1"
    local vStart=$(date +"%s")
    local vEnd
    traverse_dir "${vPath}" "false"
    vEnd=$(date +"%s")
    echo "start time: ${vStart} end time: ${vEnd}"
    echo "total time: $((vEnd-vStart)) seconds"
    close_fifo
}

main "$@"

最终版本在单纯的遍历规模不大的目录,然后打印文件名称的时候速度可能比单进程的速度要慢,但是在目录规模较大,并且do_something的时间比较久的时候,处理速度是远远大于单进程处理的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值