在开始新的子进程之前等待子进程完成
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
我如何才能等到 shell 脚本 运行ning 在 gnome-terminal
实例中完成?
我的第一个想法是,一旦他们的工作完成,我可能需要从旧终端发送信号。
如果我理解正确...
我认为您可以使用 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
我不太清楚为什么你必须为每个工作开始一个新的 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
将用 运行 命令之前的文件名替换 [=62] =][2]。在上面的示例中,xargs
扫描命令字符串 (gnome-terminal --wait -e 'bash -c "my_profiler {}"'
) 以查找 {}
并将找到的实例替换为来自标准输入 (echo "${jobs}" | ...
) 的第一个文件。然后执行生成的字符串。 xargs
将执行此操作三次 (-P3
),然后它开始等待至少一个进程完成。如果发生这种情况,xargs
将开始下一个过程。
[1]: 来自 man xargs
-P max-procs
, --max-procs=max-procs
Run up to max-procs processes at a time; the default is 1. If max-procs
is 0, xargs
will run as many processes as possible at a time. Use the -n
option or the -L
option with -P
; otherwise chances are that only one exec will be done. While xargs
is running, you can send its process a SIGUSR1
signal to increase the number of commands to run simultaneously, or a SIGUSR2
to decrease the number. You cannot increase it above an implementation-defined limit (which is shown with --show-limits
). You cannot decrease it below 1. xargs
never terminates its commands; when asked to decrease, it merely waits for more than one existing command to terminate before starting another.
Please note that it is up to the called processes to properly manage parallel access to shared resources. For example, if more than one of them tries to print to stdout, the ouptut will be produced in an indeterminate order (and very likely mixed up) unless the processes collaborate in some way to prevent this. Using some kind of locking scheme is one way to prevent such problems. In general, using a locking scheme will help ensure correct output but reduce performance. If you don't want to tolerate the performance difference, simply arrange for each process to produce a separate output file (or otherwise use separate resources).
[2]: 来自 man xargs
-I replace-str
Replace occurrences of replace-str
in the initial-arguments with names read from standard input. Also, unquoted blanks do not terminate input items; instead the separator is the newline character. Implies -x
and -L 1
.
另一种方法,因为根据进程 table 中的子字符串计算进程可能会有问题。特别是如果您在脚本中启动子流程,计数可能不可靠。
您还写道,进程 运行 持续了 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=
jobdir=
shift 2
while [[ $(ls ${jobdir}/*.running.* | wc -l) -lt ${num_jobs} -a $# -gt 0 ]] ; do
job_file=
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,并且需要在成功完成后删除该文件。文件名在作业参数之后作为额外参数传递(因此它应该是参数 2)。
这些更改如下所示:
# at the very beginning of your script
process_file=${2//\.open/}.running.$$
mv ${process_file}
# at the very end of your script, if everything went fine
rm ${process_file}
Each file takes about 2 days to process
运行 它们在图形中 window 是最昂贵的操作。刷新终端 window 可能很昂贵,如果您的进程输出大量标准输出(如 cp -vr /bigfolder /anotherfolder
),您将看到性能差异。此外,运行使用后台作业使 X 应用程序依赖于 X 服务器 - 如果您的 X 服务器崩溃,您将失去工作。这一切都与您正在尝试做的事情无关。
对于单个 运行 工作负载(运行&忘记),我会选择 xargs -Pjobs
。我会添加一些 ionice nice
以使系统可用白色进程是 运行ning。进程 stdout 输出可以被丢弃,与一些添加的前缀 ex 交错。 | sed 's/^/'"${job}: "'/'
,保存到文件中。或者更好,| logger
重定向到系统记录器。
如果这是一份一次性工作,我会打开一个 tmux
或 screen
会话,输入:
printf "%s\n" $jobs | ionice nice xargs -t -P$(nproc) sh -c 'my_profiler ""' --
并丢弃 tmux
或 screen
会话供以后使用。 3天后在我的phone上设置闹钟,3天后查看。
ionice nice
将使您的系统在处理进程时以某种方式可用。 -P$(nproc)
将进程限制为核心数。如果 my_profiler
高度 I/O 依赖并且您不关心 运行 作业时的系统性能,有时建议 运行 更多的作业然后核心,因为他们无论如何都会阻止 I/O。
您可以将 | logger -p local0.info --id=$$
添加到 xargs
之后或在子 sh
shell 内部 xargs
的末尾,这样它将输出重定向到系统日志使用 local0.info
优先级和当前 shell.
的 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
中的插入服务文件。然后用 printf "%s\n" $jobs | xargs -I{} -t systemctl start ./my_profiler@{}.service
.
开始 运行ning 它
这样我就可以从 journalctl -u my_profiler@job.service
获得我需要的所有日志,并且日志永远不会填满我的磁盘 space 的 100%,因为 journalctl
会检查这一点。使用 systemd list-failed
或 systemd status my_profiler@job.service
.
可以很容易地报告和检查错误
我必须处理十个非常大的文件。 my_profiler
处理每个文件大约需要两天时间。我可以并行化工作,以便 my_profiler
运行 分别处理每个文件,从而使用我系统的所有核心。我并行化工作的方法是 运行 同时在三个不同的终端中处理三个进程。我无法一次处理超过四个文件,或者我的系统开始变得无响应(挂起)。
我的目标是编写一个 shell 脚本,以大小为 3 的批次处理十个文件。一旦一个文件的处理完成,终端应该关闭,新文件的处理应该在另一个终端开始。作为终端,我想使用 gnome-terminal
.
目前我被以下脚本所困,运行所有进程并行:
for j in $jobs
do
gnome-terminal -- bash -c "my_profiler $j"
done
我如何才能等到 shell 脚本 运行ning 在 gnome-terminal
实例中完成?
我的第一个想法是,一旦他们的工作完成,我可能需要从旧终端发送信号。
如果我理解正确...
我认为您可以使用 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
我不太清楚为什么你必须为每个工作开始一个新的 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
将用 运行 命令之前的文件名替换 [=62] =][2]。在上面的示例中,xargs
扫描命令字符串 (gnome-terminal --wait -e 'bash -c "my_profiler {}"'
) 以查找 {}
并将找到的实例替换为来自标准输入 (echo "${jobs}" | ...
) 的第一个文件。然后执行生成的字符串。 xargs
将执行此操作三次 (-P3
),然后它开始等待至少一个进程完成。如果发生这种情况,xargs
将开始下一个过程。
[1]: 来自 man xargs
-P max-procs
,--max-procs=max-procs
Run up to max-procs processes at a time; the default is 1. If
max-procs
is 0,xargs
will run as many processes as possible at a time. Use the-n
option or the-L
option with-P
; otherwise chances are that only one exec will be done. Whilexargs
is running, you can send its process aSIGUSR1
signal to increase the number of commands to run simultaneously, or aSIGUSR2
to decrease the number. You cannot increase it above an implementation-defined limit (which is shown with--show-limits
). You cannot decrease it below 1.xargs
never terminates its commands; when asked to decrease, it merely waits for more than one existing command to terminate before starting another.Please note that it is up to the called processes to properly manage parallel access to shared resources. For example, if more than one of them tries to print to stdout, the ouptut will be produced in an indeterminate order (and very likely mixed up) unless the processes collaborate in some way to prevent this. Using some kind of locking scheme is one way to prevent such problems. In general, using a locking scheme will help ensure correct output but reduce performance. If you don't want to tolerate the performance difference, simply arrange for each process to produce a separate output file (or otherwise use separate resources).
[2]: 来自 man xargs
-I replace-str
Replace occurrences of
replace-str
in the initial-arguments with names read from standard input. Also, unquoted blanks do not terminate input items; instead the separator is the newline character. Implies-x
and-L 1
.
另一种方法,因为根据进程 table 中的子字符串计算进程可能会有问题。特别是如果您在脚本中启动子流程,计数可能不可靠。 您还写道,进程 运行 持续了 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=
jobdir=
shift 2
while [[ $(ls ${jobdir}/*.running.* | wc -l) -lt ${num_jobs} -a $# -gt 0 ]] ; do
job_file=
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,并且需要在成功完成后删除该文件。文件名在作业参数之后作为额外参数传递(因此它应该是参数 2)。 这些更改如下所示:
# at the very beginning of your script
process_file=${2//\.open/}.running.$$
mv ${process_file}
# at the very end of your script, if everything went fine
rm ${process_file}
Each file takes about 2 days to process
运行 它们在图形中 window 是最昂贵的操作。刷新终端 window 可能很昂贵,如果您的进程输出大量标准输出(如 cp -vr /bigfolder /anotherfolder
),您将看到性能差异。此外,运行使用后台作业使 X 应用程序依赖于 X 服务器 - 如果您的 X 服务器崩溃,您将失去工作。这一切都与您正在尝试做的事情无关。
对于单个 运行 工作负载(运行&忘记),我会选择 xargs -Pjobs
。我会添加一些 ionice nice
以使系统可用白色进程是 运行ning。进程 stdout 输出可以被丢弃,与一些添加的前缀 ex 交错。 | sed 's/^/'"${job}: "'/'
,保存到文件中。或者更好,| logger
重定向到系统记录器。
如果这是一份一次性工作,我会打开一个 tmux
或 screen
会话,输入:
printf "%s\n" $jobs | ionice nice xargs -t -P$(nproc) sh -c 'my_profiler ""' --
并丢弃 tmux
或 screen
会话供以后使用。 3天后在我的phone上设置闹钟,3天后查看。
ionice nice
将使您的系统在处理进程时以某种方式可用。 -P$(nproc)
将进程限制为核心数。如果 my_profiler
高度 I/O 依赖并且您不关心 运行 作业时的系统性能,有时建议 运行 更多的作业然后核心,因为他们无论如何都会阻止 I/O。
您可以将 | logger -p local0.info --id=$$
添加到 xargs
之后或在子 sh
shell 内部 xargs
的末尾,这样它将输出重定向到系统日志使用 local0.info
优先级和当前 shell.
我认为更好的方法是创建一个 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
中的插入服务文件。然后用 printf "%s\n" $jobs | xargs -I{} -t systemctl start ./my_profiler@{}.service
.
这样我就可以从 journalctl -u my_profiler@job.service
获得我需要的所有日志,并且日志永远不会填满我的磁盘 space 的 100%,因为 journalctl
会检查这一点。使用 systemd list-failed
或 systemd status my_profiler@job.service
.