我必须处理十个非常大的文件。每个文件大约需要两天的时间my_profiler
处理。我可以并行化工作,以使my_profiler
分别在每个文件上运行,从而使用系统的所有核心。我使工作并行化的方法是同时在三个不同的终端中运行三个流程。我一次不能处理四个以上的文件,否则我的系统开始变得无响应(挂起)。
我的目标是编写一个Shell脚本,以批处理大小为3的十个文件。一旦完成一个文件的处理,应关闭终端,并在另一终端中开始处理新文件。作为终端,我想使用gnome-terminal
。
当前,我受以下脚本的约束,该脚本可以并行运行所有进程:
for j in $jobs
do
gnome-terminal -- bash -c "my_profiler $j"
done
如何等待gnome-terminal
实例中运行的shell脚本完成?
我的第一个想法是,一旦完成工作,我可能需要从旧终端发送信号。
答案 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
重定向到系统记录器。
如果这是一次工作,我将打开一个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=$$
添加到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
会进行检查。使用journalctl
或systemd list-failed
可以很容易地报告和检查错误。
答案 3 :(得分:0)
另一种方法,因为基于进程表中的子字符串对进程进行计数可能会出现问题。特别是如果您在脚本中启动子流程,则计数可能会不可靠。 您还写道,该过程运行了2天,因此有时可能会出现问题,需要从优先级重新启动。
您可以用稍微复杂一点的方式进行操作。您需要一个脚本来启动您的进程,并在它们看起来仍然正常时对其进行监视(该进程没有崩溃->否则它将重新启动它们)。这需要一个初始化脚本,一个填充进程队列的脚本以及对探查器脚本的一些小的修改。
创建一个作业目录,每个作业一个文件,以便自动跟踪进度。如果可以毫无问题地处理所有作业,则稍后会自动将其删除。
#!/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}