等待子进程完成,然后再开始新的子进程

时间:2019-08-28 11:13:00

标签: bash shell wait gnome-terminal

我必须处理十个非常大的文件。每个文件大约需要两天的时间my_profiler处理。我可以并行化工作,以使my_profiler分别在每个文件上运行,从而使用系统的所有核心。我使工作并行化的方法是同时在三个不同的终端中运行三个流程。我一次不能处理四个以上的文件,否则我的系统开始变得无响应(挂起)。

我的目标是编写一个Shell脚本,以批处理大小为3的十个文件。一旦完成一个文件的处理,应关闭终端,并在另一终端中开始处理新文件。作为终端,我想使用gnome-terminal

当前,我受以下脚本的约束,该脚本可以并行运行所有进程:

for j in $jobs
do
    gnome-terminal -- bash -c "my_profiler $j"
done

如何等待gnome-terminal实例中运行的shell脚本完成?

我的第一个想法是,一旦完成工作,我可能需要从旧终端发送信号。

4 个答案:

答案 0 :(得分:3)

我不太确定为什么您必须为每个作业开始一个新的gnome-terminal。但您可以将xargs-P [1] 结合使用。同时并行运行三个my_profiler

echo "${jobs}" | xargs -P3 -I{} gnome-terminal --wait -e 'bash -c "my_profiler {}"'

此处重要的是以gnome-terminal开始--wait,否则终端会自我妖魔化,这将导致xargs开始下一个过程。 gnome-terminal 3.27.1中引入了--wait

-I{}的{​​{1}}选项定义了占位符(xargs),{}将在运行命令 [2] xargs >。在上面的示例中,xargs扫描命令字符串(gnome-terminal --wait -e 'bash -c "my_profiler {}"')中的{},并将找到的实例替换为来自stdin(echo "${jobs}" | ...)的第一个文件。然后执行结果字符串。 xargs将执行三遍(-P3),然后开始等待至少一个过程完成。如果发生这种情况,xargs将开始下一个过程。


[1]:来自man xargs

  

-P max-procs--max-procs=max-procs

     

一次运行max-procs进程;默认值为1。如果max-procs为0,则xargs一次将运行尽可能多的进程。将-n选项或-L选项与-P一起使用;否则,只有一名高管会被执行。当xargs运行时,您可以向其进程发送SIGUSR1信号以增加要同时运行的命令的数量,或向SIGUSR2发送以减少数量。您不能将其增加到实现定义的限制之上(显示为--show-limits)。您不能将其降低到1以下。xargs永不终止其命令;当被要求减少时,它仅等待一个以上的现有命令终止,然后再启动另一个。

     

请注意,取决于被调用的进程来适当地管理对共享资源的并行访问。例如,如果其中一个以上试图打印到标准输出,则输出将以不确定的顺序(很可能混合在一起)生产,除非流程以某种方式进行协作以防止这种情况。使用某种锁定方案是防止此类问题的一种方法。通常,使用锁定方案将有助于确保正确的输出,但会降低性能。如果您不想容忍性能差异,只需安排每个进程以生成单独的输出文件(或使用单独的资源)即可。

[2]:来自man xargs

  

-I replace-str

     

用从标准输入中读取的名称替换初始参数中replace-str的出现。同样,未加引号的空格也不会终止输入项目。相反,分隔符是换行符。表示-x-L 1

答案 1 :(得分:1)

如果我理解这一权利...

我认为您可以使用wait $job来完成工作。

这是一个例子。 以下脚本将最大启动。 3个工作并行,在后台。 一旦这3个工作之一结束,它将开始另一个工作。

#!/bin/bash

THREADS='3';
FILES=$(find source_dir_path -type f -name "your files*")

for file in ${FILES}
do
 NUMPROC=$(ps -ef |grep -i [y]our_process_name| wc -l |tr -d ' ')
 while (( $NUMPROC >= 3))
 do
  sleep 60
  NUMPROC=$(ps -ef |grep -i [y]our_process_name| wc -l |tr -d ' ')
 done
 echo "Starting: " $file;
 #your file processing command below, I assume this would be:
 my_profiler $file &
done

for job in `jobs -p`
do
 wait $job
done

答案 2 :(得分:1)

  

每个文件大约需要2天的处理时间

在图形窗口中运行它们是最昂贵的操作。刷新终端窗口可能会很昂贵,如果您的进程输出大量标准输出(如cp -vr /bigfolder /anotherfolder),您将看到性能差异。另外,运行带有后台作业的X应用程序使其依赖于X服务器-如果X服务器崩溃,则您将失去工作。这与您要尝试的工作无关。

对于单次运行工作负载(运行并忘记),我会选择xargs -Pjobs。我将添加一些ionice nice以使系统在进程运行时可用。进程stdout输出可以被丢弃,与某些前缀ex交织。与| sed 's/^/'"${job}: "'/'一起保存到文件中。或者更好的做法是,| logger重定向到系统记录器。

如果这是一次工作,我将打开一个tmuxscreen会话,键入:

printf "%s\n" $jobs | ionice nice xargs -t -P$(nproc) sh -c 'my_profiler "$1"' --

并丢弃tmuxscreen会话以供以后使用。在3天内在我的手机上设置一个闹钟,并在3天内再检查一次。

ionice nice将使您的系统在处理过程时以某种方式可用。 -P$(nproc)会将进程限制为内核数。如果my_profiler高度依赖I / O,并且您在运行作业时不关心系统性能,则建议运行比核心更多的作业有时是可取的,因为它们仍然会阻塞I / O。

您可以将| logger -p local0.info --id=$$添加到xargs之后的末尾或sh内的子外壳xargs中,以便使用{{1 }}当前Shell的PID的优先级和ID。

我认为更好的方法是创建一个systemd服务文件。创建这样的local0.info文件:

my_profiles@.service

使用[Unit] Description=Run my_profiler for %i [Service] # full path to my_profiler ExecStart=/usr/bin/my_profiler %i CPUSchedulingPolicy=batch Nice=19 IOSchedulingClass=best-effort 将服务添加到搜索路径,或将其创建为systemd link my_profiler@.service中的即用服务文件。然后从/var/run/systemd/system开始运行。

这样,我可以从printf "%s\n" $jobs | xargs -I{} -t systemctl start ./my_profiler@{}.service中获取所有我需要的日志,并且日志永远不会填满我的磁盘空间的100%,因为journalctl -u my_profiler@job.service会进行检查。使用journalctlsystemd list-failed可以很容易地报告和检查错误。

答案 3 :(得分:0)

另一种方法,因为基于进程表中的子字符串对进程进行计数可能会出现问题。特别是如果您在脚本中启动子流程,则计数可能会不可靠。 您还写道,该过程运行了2天,因此有时可能会出现问题,需要从优先级重新启动。

您可以用稍微复杂一点的方式进行操作。您需要一个脚本来启动您的进程,并在它们看起来仍然正常时对其进行监视(该进程没有崩溃->否则它将重新启动它们)。这需要一个初始化脚本,一个填充进程队列的脚本以及对探查器脚本的一些小的修改。

脚本1:初始化进程

创建一个作业目录,每个作业一个文件,以便自动跟踪进度。如果可以毫无问题地处理所有作业,则稍后会自动将其删除。

#!/bin/bash
tmpdir=/tmp/
jobdir=${tmpdir}/jobs
num_jobs=3
mkdir -p ${jobdir}

i=1
for file in $jobs ; do
    ((i++))
    echo "${file}" > ${jobdir}/${i}.open
done

脚本2:启动实际进程

#!/bin/bash
jobdir=${tmpdir}/jobs
num_jobs=3

function fill_process_queue() {
    # arg1: num_jobs
    # arg2: jobdir
    # arg3...: open jobs
    num_jobs=$1
    jobdir=$2
    shift 2
    while [[ $(ls ${jobdir}/*.running.* | wc -l) -lt ${num_jobs} -a $# -gt 0 ]] ; do
        job_file=$1
        shift 1
        gnome-terminal -- bash -c "my_profiler $(cat ${jobdir}/${job_file}) ${jobdir}/${job_file}"
        # now give the called job some time to
        # mark it's territory (rename the job file)
        sleep 5s
    done
}

while [[ -z $(ls ${jobdir}) ]] ; do
    # still files present, so first check if
    # all started processes are still running
    for started_job in $(ls ${jobdir}/*.running.* 2>/dev/null) ; do
        # check if the running processes are still alive
        pid= "{started_job//[0-9]\.running\.}"
        jobid= "{started_job//\.running\.[0-9]*}"
        if ! kill -0 ${pid} 2> /dev/null ; then
            # process is not running anymore
            # don't worry kill -0 doesn't harm your
            # process
            mv ${jobdir}/${started_job} ${jobdir}/${jobid}
        fi
    done
    fill_process_queue ${num_jobs} ${jobdir} ${jobdir}/*.open
    sleep 30s
done
# if the directory is empty, it will be removed automatically by rmdir, if non-empty, it remains
rmdir ${jobdir}

分析器脚本的更改

探查器脚本需要重命名作业文件,因此它在脚本开始时包含探查器脚本的pid,一旦成功完成文件,则需要删除该文件。文件名在job参数之后作为附加参数传递(因此它应该是参数2)。 这些变化如下:

# at the very beginning of your script
process_file=${2//\.open/}.running.$$
mv $2 ${process_file}

# at the very end of your script, if everything went fine
rm ${process_file}