[英]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
重定向到系统记录器。
如果是一次性工作,我将打开tmux
或screen
会话,键入:
printf "%s\n" $jobs | ionice nice xargs -t -P$(nproc) sh -c 'my_profiler "$1"' --
并丢弃tmux
或screen
会话以供以后使用。 在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-failed
或systemd status my_profiler@job.service
可以轻松地报告和检查错误。
还有另一种方法,因为基于进程表中的子字符串对进程进行计数可能会有问题。 特别是如果您在脚本中启动子流程,则计数可能会不可靠。 您还写道,该进程运行了2天,因此有时可能会出现问题,需要从优先级重新启动。
您可以用稍微复杂一些的方式完成该操作。 您需要一个脚本来启动您的进程,并在它们看起来仍然正常时对其进行监视(该进程没有崩溃->否则它将重新启动它们)。 这需要一个初始化脚本,一个填充进程队列的脚本以及对Profiler脚本的少量修改。
创建一个作业目录,每个作业一个文件,以便自动跟踪进度。 如果可以毫无问题地处理所有作业,则稍后会自动将其删除。
#!/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
#!/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.