스크립트의 다중 백그라운드 프로세스

스크립트의 다중 백그라운드 프로세스

일부 파일을 복사해야 하는데 시간이 오래 걸리는 상황이 발생하면 파일 복사본에 대해 병렬 처리를 수행해야 한다고 가정해 보겠습니다. 예를 들어 다음과 같을 수 있습니다.

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. 에서 사용 가능한 쉘 프로세스 대체는 ksh이를 달성하는 데 도움이 될 수 zsh있으며 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, 동시에 시작되어 파이프라인과 상호 연결됩니다 j2.cat

cat그러나 두 번째 파이프(작성자)에서 읽는 작업은 완료된 후에만 시작 됩니다 j2. j1즉, j2파이프 크기(예: Linux에서는 일반적으로 64kiB)보다 더 많은 로그 레코드가 기록 되면 완료될 j2때까지 차단됩니다 j1.

sponge이는 from 을 사용하여 방지할 수 있습니다 moreutils. 예를 들면 다음과 같습니다.

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

이는 모든 출력이 메모리에 저장되고 cat은 j2in이 완료된 후에만 출력 쓰기를 시작한다는 것을 의미하지만, 이 경우 예제를 사용하는 것이 더 나을 수 있습니다.j2jobs.logj2pv -qB 100M

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

이 접근 방식은 아직 완료되지 않은 경우 j2출력(및 두 파이프의 내용 포함)을 기록한 후에만 일시 중지하고 j1표준 출력으로의 출력이 완료될 때까지 기다리지 않습니다.100Mpvj2

위의 모든 것과 마찬가지로 대부분의 명령 출력을 파일이나 파이프(tty 이외의 모든 것)로 리디렉션하면 동작이 영향을 받는다는 점에 유의해야 합니다. 이러한 명령 또는 호출하는 stdioAPI libc( printf, fputs, fwrite...)는 출력이 터미널로 전송되지 않음을 감지하고 이러한 표준 실수를 수행하지 않는 동안 큰 출력 청크(킬로바이트)를 전달하여 최적화를 수행합니다. 이는 출력 및 오류 메시지의 순서가 영향을 받는다는 것을 의미합니다. 이것이 문제가 되면 GNU 시스템이나 FreeBSD(적어도) 및 동적 링크 명령의 경우 다음을 사용할 수 있습니다 stdbuf.

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

바꾸다:

j1 > j1.log 2>&1

stdio 출력이 라인 버퍼링되어 있는지 확인하십시오(각 출력 라인은 완료되는 즉시 개별적으로 기록됩니다).

옵션 2의 경우 PIPE_BUF바이트보다 작은 파이프(Linux에서는 4096바이트, 로그의 평균 줄보다 훨씬 큼)에 쓰는 것이 원자성이 보장됩니다. 즉, 두 프로세스가 동시에 동일한 파이프에 쓰는 경우 그들의 글은 원자성이 보장됩니다. 두 쓰기가 서로 얽히지 않도록 보장됩니다. 일반 파일에 대해서는 그러한 보장이 없지만 4kiB보다 작은 2개의 쓰기가 어떤 OS나 파일 시스템에서 얽힐 수 있는지 심각하게 의심됩니다.

따라서 위에서 설명한 버퍼링 없이 로그 줄이 전체적으로 개별적으로 출력되는 경우 줄 출력에 이 작업에 대한 줄의 일부이자 다른 작업의 일부인 경로가 없는 것이 보장됩니다.

그러나 명령이 작성 중인 줄의 두 부분(예: ) 사이를 플러시하는 것을 방지할 수 있는 방법은 없으며 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의 쉘에서는 작동하지 않습니다 ).catj1j2

위에서 우리는 | cat원자성을 보장하는 파이프라인을 사용했습니다. 각 명령의 출력을 명령으로 파이프합니다.상표각 줄에는 직위가 있습니다. 필요에 따라 출력을 작성할 수 있습니다. j1왜냐하면 ( 태그 접두사가 있는) 행이 전체적으로 개별적으로 출력되므로 출력이 손상되지 않도록 보장하기 때문입니다.j2sedstdbuf -oL

stdbuf -oL위와 동일한 설명이 여전히 적용됩니다. 우리는 명령 에 적용하지 않으므로 출력을 버퍼링할 가능성이 높으므로 생성된 후 오랜 시간이 지날 때까지 기록되지 않을 수 있습니다 j1. j2이는 다음과 같은 이유로 이전 사례보다 더 나쁩니다.

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

이는 B가 실행되기 전에 A가 실행된다는 것을 의미 j1하지만 C가 실행되기 전에 명령을 실행한다는 의미는 아닙니다. 따라서 문제가 발생하면 j2더 많은 명령을 적용해야 할 수도 있습니다.stdbuf -oL

이와 같은 쉘 기능 에는 적용할 수 없지만 stdbuf적어도 GNU 및 FreeBSD stdbuf에서는 이를 사용하여 전역적으로 또는 서브쉘별로 설정할 수 있습니다.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)

관련 정보