用于multithreading进程的Shell脚本

我是一名生物信息学家,最近陷入了一个需要一些脚本来加速我的过程的问题。 我们有一个名为PHASE和Command的软件,我input我的命令行来启动软件

./PHASE test.inp test.out 

其中PHASE是程序的名称,test.ip是input文件,test.out是输出文件。需要一个核心运行上述过程,大约需要3个小时才能完成。

现在我有1000input文件说test1.inp,test2.inp,test3.inp …..等等test1000.inp,并希望生成所有1000个输出文件.. test1.out,test2.out .. … test100.out使用我的系统有四个核心的全部容量。

要使用我的系统的全部容量,我想打开4个上面的脚本的实例,需要这样的4个input文件…并生成4个不同的输出

 ./PHASE test1.inp test1.out ./PHASE test2.inp test2.out ./PHASE test3.inp test3.out ./PHASE test4.inp test4.out 

每个作业完成后,输出文件已经生成的脚本应该再次启动剩余的input文件,直到所有结束。

 ./PHASE test5.inp test5.out ./PHASE test6.inp test6.out ./PHASE test7.inp test7.out ./PHASE test8.inp test8.out 

等等…..

如何为上述脚本编写脚本,脚本利用4个内核并加速我的过程。

如果你有GNU xargs,可以考虑如下的东西:

 printf '%s\0' *.inp | xargs -0 -P 4 -n 1 \ sh -c 'for f; do ./PHASE "$f" "${f%.inp}.out"' _ 

-P 4在这里很重要,表示并行运行的进程数。

如果您有大量的输入并且处理速度很快,请考虑用更大的数字替换-n 1 ,以增加每个外壳实例迭代的输入数量 – 降低外壳启动成本,但也会降低粒度和,潜在的并行性水平。


也就是说,如果你真的想要四个批次(每个问题),让所有四个完成之前开始下四个(这引入了一些低效率,但是你所要求的),你可以做这样的事情…

 set -- *.inp # set $@ to list of files matching *.imp while (( $# )); do # until we exhaust that list... for ((i=0; i<4; i++)); do # loop over batches of four... # as long as there's a next argument, start a process for it, and take it off the list [[ $1 ]] && ./PHASE "$1" "${1%.imp}.out" & shift done wait # ...and wait for running processes to finish before proceeding done 

我的钱在GNU平行 ,而不是壳hackery ! 很好的词@威廉Pursell!

它看起来像这样:

 parallel ./PHASE test{1}.inp test{1}.out ::: {1..1000} 

它是:

  • 容易写
  • 易于阅读
  • 高性能
  • 灵活

如果你想一次运行16个工作,只需要像这样添加-j

 parallel -j 16 ./PHASE ... 

如果你想获得进度报告,只需要添加-progress ,就像这样:

 parallel --progress ./PHASE ... 

如果你想在你的网络中添加一些额外的服务器来加快速度,只需要把他们的IP地址加上-S ,就像这样:

 parallel -S meatyserver1 -S meatyserver1 ./PHASE ... 

如果你想记录进程何时开始以及什么时候完成,只需要这样做:

 parallel --joblog $HOME/parallelLog.txt 

如果你想添加检查点,所以你的工作可以停止和重新启动,你几乎可以肯定应该用3000小时的处理,这也很容易。 有许多变体,但是例如,您可以跳过相应的输出文件已经存在的作业,这样如果您重新启动,您可以立即继续执行。 我会做一个小小的bash函数,像这样做:

 #!/bin/bash # Define a function for "GNU Parallel" to call checkpointedPHASE() { ip="test${1}.inp" op="test${1}.out" # Skip job if already done if [ -f "$op" ]; then echo Skipping $1 ... else ./PHASE "$ip" "$op" fi } export -f checkpointedPHASE # Now start parallel jobs parallel checkpointedPHASE {1} ::: {1..1000} 

你在GNU并行 生物信息学教程中使用GNU并行生物信息学 。

“多线程”对于你正在做的事情来说是错误的。 你想要并行运行多个进程。 多线程是指在同一进程中运行多个执行线程。 已经提到了一次运行所有的进程,并让os为你安排它,就像xargs -P ,你可能想看看gnu parallel 。 你也可以在shell中破解一个解决方案,但是这有几个问题(即,它甚至不是强健的)。 基本的想法是创建一个管道,并让每个进程在管道中写入一个令牌。 同时,您读取管道并在出现令牌时启动新的进程。 例如:

 #!/bin/bash n=${1-4} # Use first arg as number of processes to run, default is 4 trap 'rm -vf /tmp/fifo' 0 rm -f /tmp/fifo mkfifo /tmp/fifo cmd() { ./PHASE test$1.inp test$1.out echo $1 > /tmp/fifo } # spawn first $n processes yes | nl | sed ${n}q | while read num line; do cmd $num & done # Spawn a new process whenever a running process terminates yes | nl | sed -e 1,${n}d -e 1000q | { while read num line; do read -u 5 stub # wait for one to terminate cmd $num & done 5< /tmp/fifo wait } & exec 3> /tmp/fifo wait 

Bash不支持多线程,但它支持多处理。 如果你改变你的命令是:

 for i in {1..1000}; do ./PHASE test$i.inp test$i.out & done 

这将运行每个进程,您的计算机将根据您有多少核心自动计划它们。 1000进程相比线程会有很多开销,但虽然不理想,但应该还是可以的。

编辑:这是一个更高级的方法,如果你想优先获得先进的答案:

 #!/bin/bash # Number of cores and range end n=4 e=1000 # This function will do the processing process() { for ((i=$1; i <= $3; i += $2)); do ./PHASE test${i}.inp test${i}.out echo "Done $i" done } # For each core create a process and record the pid for ((i=1; i <= n; i++)); do process $i $n $e & done # Wait for each process to complete wait