脚本中的多个后台进程

脚本中的多个后台进程

假设如果我遇到需要复制某些文件并且需要很长时间的情况,那么我必须对文件副本进行并行处理。例如它可能看起来像这样

for i in ipaddresslist
do

cp x y &  //running in back ground or some other process in background

done

wait  //will cause till all the files are copied or whatever process if all are finished

现在我已经使用了wait 所有后台进程都已完成,但其中有这样的情况

1)某些文件复制可能会更早发生,因此如果我必须对这些文件进行一些处理,我必须等到所有文件都被复制。

2) 如果复制进程(或在后台运行的任何其他程序)写入日志文件,则日志文件可能会因每个后台进程同时尝试写入该文件而出现乱码。

对于此类事情是否有任何解决方法,1)我的意思是,如果我知道该过程已完成,那么我可以开始对该过程的特定实例(例如文件复制)进行其余的处理(如果其完成)。此外,写入日志文件也可以按顺序进行。

请提出建议

答案1

如果复制某些文件后需要启动某些作业,只需将其作为后台作业的一部分即可:

(cp this /there && start job that needs this in /there) &
(cp that /here && start job that needs that in /here) &
wait

(最后一个&不是必需的)。

现在,对于更复杂的依赖关系,您可以使用 GNU make -j

make -j2 -f /dev/fd/3 3<< 'EOF'
all: j1 j2 j3
.PHONY: cp1 cp2 cp3 j1 j2 j3 all

cp1:
    cp this /there

cp2:
    cp that /here

cp3:
    cp this /here

j1: cp1
    start job that needs this in /there

j2: cp2
    start job that needs that in /here

j3: cp1 cp3
    start job that needs this in /here and /there
EOF

-j2在任何给定时间最多运行 2 个作业,并且会尊重依赖性。

现在为了避免日志文件出现乱码,您有两个主要选项

  1. 不要交错它们,即将每项作业的内容一个接一个地附加。
  2. 尝试确保它们很好地交错,可能会标记每个作业的每一行,以便更容易查看哪一行属于哪个作业。

对于 1,最简单的是将每个作业输出存储在单独的文件中,然后将它们合并:

(cp this /there && start job that needs this in /there) > j1.log 2>&1 &
(cp that /here && start job that needs that in /here) > j2.log 2>&1 &
wait
cat j1.log j2.log > jobs.log

另一种选择是使用管道来收集每个作业的输出并合并cat它们。中可用的 Shell 进程替换kshzsh或者bash可以帮助我们实现这一点,甚至可以处理后台:

j1() { cp this /there && start job that needs this in /there; }
j2() { cp that /here && start job that needs that in /here; }
cat <(j1 2>&1) <(j2 2>&1) > jobs.log

j1,同时启动并与管道互连j2cat

但请注意,只有在完成后cat才会开始从第二个管道(由 写入j2)读取。j1这意味着如果j2写入的日志记录多于管道的大小(例如,在 Linux 上,通常为 64kiB),j2则将被阻止直到j1完成。

sponge可以通过使用from 来避免这种情况moreutils,例如:

cat <(j1 2>&1) <(j2 2>&1 | sponge) > jobs.log

尽管这意味着所有的输出都将存储在内存中,并且 cat 仅在完成后才开始写入inj2的输出,在这种情况下使用example 可能更可取:j2jobs.logj2pv -qB 100M

cat <(j1 2>&1) <(j2 2>&1 | pv -qB 100M) > jobs.log

这种方式j2只会在日志输出(加上两个管道内容)j1之后暂停(如果尚未完成) ,并且不会等到输出到标准输出之前完成。100Mpvj2

请注意,对于上述所有情况,您需要注意,一旦将大多数命令的输出重定向到文件或管道(除了 tty 之外的任何内容),行为就会受到影响。这些命令,或者更确切地说,它们调用stdio的 API libc( printffputsfwrite...) 检测到输出不会发送到终端,并通过大块(几千字节)输出来执行优化,而它们不这样做标准错误。这意味着输出和错误消息的顺序将受到影响。如果这是一个问题,在 GNU 系统或 FreeBSD(至少)上以及对于动态链接命令,您可以使用以下命令stdbuf

stdbuf -oL j1 > j1.log 2>&1

代替:

j1 > j1.log 2>&1

确保 stdio 输出是行缓冲的(每行输出将在完成后立即单独写入)。

对于选项 2,写入小于PIPE_BUF字节的管道(在 Linux 上为 4096 字节,比日志的平均行大得多)保证是原子的,也就是说,如果两个进程同时写入同一个管道,则它们的写入将被保证是原子的。保证2次写入不会交织在一起。常规文件没有这样的保证,但我严重怀疑小于 4kiB 的 2 次写入最终可能会在任何操作系统或文件系统上交织在一起。

因此,如果没有上述的缓冲,并且如果日志行作为一个整体和单独地单独输出,那么您可以保证输出的行不会有该作业的一部分行和一部分其他工作的路线。

但是,没有什么可以阻止命令在正在写入的行的两个部分之间进行刷新(例如printf("foo"); fflush(stdout); printf("bar\n");),并且 stderr 上没有缓冲。

另一个问题是,一旦所有作业的线路交错,就很难区分哪条线路对应哪个作业。

您可以通过执行以下操作来解决这两个问题:

tag() { stdbuf -oL sed "s%^%$1: %"; }
{
  j1 2>&1 | tag j1 &
  j2 2>&1 | tag j2
} | cat > jobs.log

(请注意,我们不需要wait(并且它在大多数 shell 中都不起作用),因为cat直到没有人再写入管道时才会完成,所以直到j1j2终止)。

上面我们使用了| cat具有原子性保证的管道。我们将每个命令的输出通过管道传输到一个命令标签每行都有工作名称。j1j2可以根据需要编写输出,sed(因为stdbuf -oL)会将行(带有标记前缀)作为整体和单独输出,这将保证输出不会被破坏。

与上面相同的注释仍然适用:我们不应用于stdbuf -oL命令j1j2因此它们很可能会缓冲其输出,因此可能会在生成后很长时间才写入。这比前一个情况更糟糕,因为如果您看到:

j1: doing A
j1: doing B
j2: doing C

这确实意味着在j1执行 B 之前执行了 A,但并不是说它在执行 C 之前执行了其中任何命令。因此,如果出现问题j2,您可能需要应用更多命令。stdbuf -oL

请注意,您不能应用于类似或上面的stdbufshell 函数,但至少对于 GNU 和 FreeBSD stdbuf,您可以使用它来全局或基于每个子 shell 进行设置:j1j2stdbuf

stdbuf_LD_PRELOAD=$(stdbuf sh -c 'export -p LD_PRELOAD')
line_buffered_output() {
  eval "$stdbuf_LD_PRELOAD"
  export _STDBUF_O=L
}
j1() (line_buffered_output; cp this /there && start...)

答案2

如果每个下载的文件都需要处理,请执行以下操作:

cp whatever file; process file &

反而。

如果您担心日志文件出现乱码,也许您应该使用syslog(3),也许通过logger(1).nohup(1)如果有人可以注销(并无意中终止后台运行的进程),请考虑使用。

相关内容