2011-12-10 2 views
2

내 주요 목표는 개체의 큰 목록에 대한 일부 (외부 시간이 소요 비싼) 작업을 수행하는 것입니다. 그것을 위해, 내가 그것을 곧장 앞으로하면, 그것은 많은 시간이 걸린다. 그래서 나는 병렬 모드로 가서 자식 프로세스들을 포크 화 (fork, fork, fork)하고 더 작은 세트의 객체에 대한 작업을 수행하기로 결정했다. 메인 (부모) 프로세스에서 원 프로세스 버전에 대해 수행했던 전반적인 통계 정보를 인쇄하고 싶었습니다.여러 하위 프로세스를 포크하고 읽는 방법은 무엇입니까?

그러나 4 개의 하위 프로세스를 포크하고 일부 작업을 수행하면 이들이 살아 있다는 것을 알 수 있지만 그 중 하나만 실제로 뭔가를 수행하고 부모에게 정보를 다시 보냅니다.

다음은 내가 지금까지 해본 코드입니다. 시간이 많이 소요되는 부분은 임의적 인 휴면과 조롱을 받았으며, 동작을 아주 잘 시뮬레이트합니다.

#!/usr/bin/env perl 
use strict; 
use warnings; 

use DateTime; 
use DateTime::Format::HTTP; 
use Time::HiRes; 

my @to_be_processed = (1..300000); 
my @queues; 
my $nprocs = 4; 

my $parent_from_child; 
my @child_from_parent; 
my @child_to_parent; 

$SIG{CHLD} = 'IGNORE'; 
$|=1; # autoflush 

my %stat = (
    total   => scalar(@to_be_processed), 
    processed  => 0, 
    time_started => [Time::HiRes::gettimeofday], 
); 

# divide the list into queues for each subprocess 
for (my $i = 0; $i < $stat{total}; $i++) { 
    my $queue = $i % $nprocs; 
    push @{$queues[$queue]}, $to_be_processed[$i]; 
} 

# for progress simulation 
srand (time^$$); 

for (my $proc = 0; $proc < $nprocs; $proc++) { 

    # set up the pipes 
    pipe $parent_from_child, $child_to_parent[$proc]  or die "pipe failed - $!"; 

    # fork 
    defined(my $pid = fork) or die "fork failed - $!"; 

    if ($pid) { 
     # parent 
     close $child_to_parent[$proc]; 
     printf("[%u] parent says: child %u created with pid %u\n", $$, $proc, $pid); 
    } 
    else { 
     # child 
     close $parent_from_child; 
     open(STDOUT, ">&=" . fileno($child_to_parent[$proc])) or die "open failed - $!"; 

     warn(sprintf("[%u] child alive with %u entries\n", $$, scalar(@{$queues[$proc]}))); 

     foreach my $id (@{$queues[$proc]}) { 
      printf("START: %s\n", $id); 

      # simulation of progress 
      my $random_microseconds = int(rand(3000000))+200000; 
      warn(sprintf("[%u] child 'works' for %u microseconds", $$, $random_microseconds)); 
      Time::HiRes::usleep($random_microseconds); 

      printf("DONE\n") 
     } 
     exit(0); 
    } 
} 

# parent: receive data from children and print overall statistics 
while (<$parent_from_child>) { 
    chomp(my $line = $_); 

    if ($line =~ m/^START: (\S+)/) { 
     my ($id) = @_; 

     printf("%6u/%6u", $stat{processed}, $stat{total}); 
     if ($stat{time_avg}) { 
      my $remaining = ($stat{total} - $stat{processed}) * $stat{time_avg}; 
      my $eta = DateTime->from_epoch(epoch => time + $remaining); 
      $eta->set_time_zone('Europe/Berlin'); 
      printf(" (ETA %s)", DateTime::Format::HTTP->format_isoz($eta)); 
     } 
     printf("\r"); 
    } 
    elsif ($line =~ /^DONE/) { 
     $stat{processed}++; 
     $stat{time_processed} = Time::HiRes::tv_interval($stat{time_started}); 
     $stat{time_avg}  = $stat{time_processed}/$stat{processed}; 
    } 
    else { 
     printf("%s\n", $line); 
    } 
} 

일반적으로 경고는 제거해야합니다. 실행하면 자식 하나만 작동한다는 것을 알 수 있습니다. 내 질문은 : 왜? 내 실수는 어디에서 발생하며 어떻게해야합니까?

감사 K.

답변

6

당신은 strace를 아래에 펄을 실행할 수 있습니다, 당신은 당신의 아이들의 생활이 매우 짧은 것을 발견하고, 다음과 같이합니다 :

:

close(3)        = 0 
ioctl(4, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff753b3a10) = -1 EINVAL (Invalid argument) 
lseek(4, 0, SEEK_CUR)     = -1 ESPIPE (Illegal seek) 
fstat(4, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0 
dup2(4, 1)        = 1 
dup(4)         = 3 
fcntl(4, F_GETFD)      = 0x1 (flags FD_CLOEXEC) 
dup2(3, 4)        = 4 
fcntl(4, F_SETFD, FD_CLOEXEC)   = 0 
close(3)        = 0 
fcntl(1, F_SETFD, 0)     = 0 
write(2, "[30629] child alive with 75000 e"..., 39) = 39 
brk(0x3582000)       = 0x3582000 
write(1, "START: 1\n", 9)    = -1 EPIPE (Broken pipe) 
--- SIGPIPE (Broken pipe) @ 0 (0) --- 

이는 이유

pipe $parent_from_child, $child_to_parent[$proc]  or die "pipe failed - $!"; 

파이프에 잘못된 인수로 배열을 사용했습니다. 부모의 모든 읽음을 열어 두어야합니다. 대신 부모가 배열을 설정하여 모든 쓰기 측면을 열어 둘 수 있습니다 (그러나 부모 블록에서는 쓰기 측면을 즉시 닫습니다). 따라서 다음에 루프를 통해 pipe이 새 핸들을 만들고 $parent_from_child에 할당합니다. 따라서 이전 값은 더 이상 참조를 가지지 않으며 perl은이를 위로 정리하여 파일 핸들을 닫습니다. 마지막으로 SIGPIPE로 죽는 것을 제외하고 여러분의 자녀들은 죽습니다.

나는 그 읽기 핸들을 다시 사용할 수 있고 여러 개의 쓰기 핸들을 할당 할 수 있다고 생각합니다. 당신은 할 수 없습니다. pipe은 항상 새 읽기 핸들과 새 쓰기 핸들을 만듭니다.

실제로 동일한 읽기 핸들을 공유하려는 경우 (두 클라이언트의 출력이 인터리브 된 경우 손상 될 수 있음) 루프 외부에서 한 번만 만듭니다. 모든 어린이는 fork을 통해 동일한 쓰기 핸들을 상속 받게됩니다. 어린이 당 1 개가 더 필요한 경우가 많으므로 select 루프를 사용하여 어떤 출력을 사용할 수 있는지 확인하고 해당 출력을 읽어야합니다.

CPAN에는 기성품 (10 개)이 있습니다.

+0

좋아요! 많은 감사합니다! 예, 저는 같은 핸들을 읽을 수 있고 플러싱과 함께 작동한다고 생각했습니다. 나는 이제 IO :: Select와 can_read() 루프를 사용하여 그것을 수행했다. 이것은 매력처럼 작동한다. –

+0

덧글 추가 : 저는 Srand Call을 하위 프로세스로 이동하는 것을 잊었습니다. 부모에서 그것을 초기화하는 것은 물론 모든 어린이들에게 동일한 순서로 이어진다. –

관련 문제