繁体   English   中英

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

[英]Wait for a child process to finish before starting new child process

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

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

目前,我受制于以下脚本,该脚本并行运行所有进程:

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

我如何才能等到在gnome-terminal实例中运行的Shell脚本完成?

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

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

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

这里重要的是用--wait启动gnome-terminal ,否则终端会自我妖魔化,这将导致xargs启动下一个进程。 --wait是在gnome-terminal 3.27.1中引入的

xargs-I{}选项定义一个占位符( {} ), xargs将在运行命令[2]之前用文件名替换。 在上面的示例中, 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

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

我认为您可以使用wait $job 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天的处理时间

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

对于单次运行工作负载(run&forget),我将使用xargs -Pjobs 我会添加一些ionice nice以使系统在进程运行时可用。 进程stdout输出可以被丢弃,与某些前缀ex交织。 | sed 's/^/'"${job}: "'/' | sed 's/^/'"${job}: "'/' ,保存到文件中。 或者更好, | logger | 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=$$ | logger -p local0.info --id=$$到结束后xargs或儿童的内部sh内壳xargs ,使得其将重定向使用输出到系统日志local0.info当前壳的PID的优先级和id。

在我看来,更好的方法是创建一个systemd服务文件。 创建这样的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 systemd link my_profiler@.service服务文件。 然后使用printf "%s\\n" $jobs | xargs -I{} -t systemctl start ./my_profiler@{}.service开始运行它printf "%s\\n" $jobs | xargs -I{} -t systemctl start ./my_profiler@{}.service printf "%s\\n" $jobs | xargs -I{} -t systemctl start ./my_profiler@{}.service

这样,我可以从journalctl -u my_profiler@job.service获取所需的所有日志,日志将永远不会填满我的磁盘空间的100%,因为journalctl进行检查。 通过systemd list-failedsystemd status my_profiler@job.service可以轻松地报告和检查错误。

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

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

脚本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}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM