일부 파일을 복사해야 하는데 시간이 오래 걸리는 상황이 발생하면 파일 복사본에 대해 병렬 처리를 수행해야 한다고 가정해 보겠습니다. 예를 들어 다음과 같을 수 있습니다.
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의 경우 가장 간단한 방법은 각 작업 출력을 별도의 파일에 저장한 다음 병합하는 것입니다.
(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은 j2
in이 완료된 후에만 출력 쓰기를 시작한다는 것을 의미하지만, 이 경우 예제를 사용하는 것이 더 나을 수 있습니다.j2
jobs.log
j2
pv -qB 100M
cat <(j1 2>&1) <(j2 2>&1 | pv -qB 100M) > jobs.log
이 접근 방식은 아직 완료되지 않은 경우 j2
출력(및 두 파이프의 내용 포함)을 기록한 후에만 일시 중지하고 j1
표준 출력으로의 출력이 완료될 때까지 기다리지 않습니다.100M
pv
j2
위의 모든 것과 마찬가지로 대부분의 명령 출력을 파일이나 파이프(tty 이외의 모든 것)로 리디렉션하면 동작이 영향을 받는다는 점에 유의해야 합니다. 이러한 명령 또는 호출하는 stdio
API 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
의 쉘에서는 작동하지 않습니다 ).cat
j1
j2
위에서 우리는 | cat
원자성을 보장하는 파이프라인을 사용했습니다. 각 명령의 출력을 명령으로 파이프합니다.상표각 줄에는 직위가 있습니다. 필요에 따라 출력을 작성할 수 있습니다. j1
왜냐하면 ( 태그 접두사가 있는) 행이 전체적으로 개별적으로 출력되므로 출력이 손상되지 않도록 보장하기 때문입니다.j2
sed
stdbuf -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에서는 이를 사용하여 전역적으로 또는 서브쉘별로 설정할 수 있습니다.j1
j2
stdbuf
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)