【问题标题】:How to make waitpid block the loop如何让waitpid阻塞循环
【发布时间】:2013-06-03 13:05:03
【问题描述】:

以下代码运行 2 个孩子,他们将等待 10 秒并终止。父级坐在一个循环中,等待子级终止:

#!/usr/bin/perl

use strict;
use warnings;
use POSIX ":sys_wait_h";

sub func
# {{{
{
      my $i = shift;
      print "$i started\n";
      $| = 1;
      sleep(10);
      print "$i finished\n";
}
# }}}

my $n = 2;
my @children_pids;

for (my $i = 0; $i < $n; $i++) {
      if ((my $pid = fork()) == 0) {
            func($i);
            exit(0);
      } else {
            $children_pids[$i] = $pid;
      }
}

my $stillWaiting;
do {
      $stillWaiting = 0;
      for (my $i = 0; $i < $n; ++$i) {
            if ($children_pids[$i] > 0)
            {
                  if (waitpid($children_pids[$i], WNOHANG) != 0) {
                        # Child is done
                        print "child done\n";
                        $children_pids[$i] = 0;
                  } else {
                        # Still waiting on this child
                        #print "waiting\n";
                        $stillWaiting = 1;
                  }
            }
            #Give up timeslice and prevent hard loop: this may not work on all flavors of Unix
            sleep(0);
      }
} while ($stillWaiting);

print "parent finished\n";

代码基于此答案:Multiple fork() Concurrency

它工作正常,但父循环正在占用处理器时间。 top 命令给出了这个:

Here 答案是:

作为额外的奖励,循环将在 waitpid 而阻塞 孩子们正在运行,因此您在等待时不需要繁忙的循环。

但对我来说它不会阻塞。怎么了?

【问题讨论】:

    标签: perl optimization waitpid


    【解决方案1】:

    由于您的父线程在等待子线程时实际上并没有做任何事情,我将其简化为

    #!/usr/bin/perl    
    use strict;
    use warnings;
    $| = 1; # autoflush
    
    sub func{
        my($i) = @_;
        print "$i started\n";
        sleep(10);
        print "$i finished\n";
    }
    
    my $n = 2;
    my @children_pids;
    
    for my $i ( 0..($n-1) ) { # faster, and clearer than the C-style for loop
        my $pid = fork;
        die "Unable to fork" unless defined $pid; # check for errors
        if ( $pid == 0) { # child
            func($i);
            exit(0); # may need to be POSIX::_exit()
        } else { # parent
            push @children_pids, $pid; # don't allow undef or 0 on the list
        }
    }
    
    # while( @children_pids ){
    #    waitpid shift @children_pids, 0;
    # }
    
    waitpid $_, 0 for @children_pids;
    
    print "parent finished\n";
    

    如果你的 perl 是用 IThreads 编译的,你可以使用 threads 模块。
    (Windows 上的fork 模拟需要 IThreads)

    使用threads 还可以更轻松地执行您最初尝试的操作,在线程完成时加入线程。

    use strict;
    use warnings;
    use threads (); # not using async
    $| = 1; # autoflush
    
    sub func{
        my($i) = @_;
        print "$i started\n";
        sleep(rand(10)); # randomize the order of completion for this example
        print "$i finished\n";
        return $i; # <===
    }
    
    my $n = 10;
    
    for my $i ( 0..($n-1) ){
       my $thread = threads->create( \&func, $i ); # ask for scalar value
       die "unable to create thread $i" unless defined $thread;
    }
    
    while( threads->list ){
        # join the threads that are done
        for my $thread ( threads->list(threads::joinable) ){
            print 'thread-id: ',  $thread->tid, ' returned: ', $thread->join, "\n";
        }
    
        # be nice to other threads and processes
        threads->yield;
    
        # allows the threads to "bunch up" for this example
        # not necessary for real code.
        sleep 2;
    }
    
    0 started
    1 started
    2 started
    3 started
    4 started
    5 started
    6 started
    7 started
    7 finished
    8 started
    9 started
    2 finished
    thread-id: 3 returned: 2
    thread-id: 8 returned: 7
    8 finished
    5 finished
    thread-id: 6 returned: 5
    thread-id: 9 returned: 8
    1 finished
    thread-id: 2 returned: 1
    3 finished
    6 finished
    9 finished
    4 finished
    thread-id: 4 returned: 3
    thread-id: 5 returned: 4
    thread-id: 7 returned: 6
    thread-id: 10 returned: 9
    0 finished
    thread-id: 1 returned: 0
    

    【讨论】:

      【解决方案2】:

      您正在传递WNOHANG 标志,这使得调用非阻塞。删除此标志,waitpid 将在 0% CPU 等待子进程退出。

      如果您采用这种方法,您可以简化代码。在孩子完成之前无需循环,因为阻塞 waitpid 调用将为您完成:

      for (my $i = 0; $i < $n; ++$i) {
          if ($children_pids[$i] > 0) {
                waitpid($children_pids[$i], 0);
                print "child done\n";
                $children_pids[$i] = 0;
          }
      }
      

      或者,将sleep 调用更改为等待一秒钟。然后你的程序将每秒检查一次完成的子进程,而不会增加 CPU 使用率。

      【讨论】:

      • 请把“)”改成底部的“}”
      • 我想我可以写成:while( @children_pids ){ my $pid = shift @children_pids; next unless $pid; waitpid $pid, 0; },如果你不允许将 0(或 undef)放在列表​​中,可以简化为:while( @children_pids ){ waitpid(shift @children_pids, 0) }
      猜你喜欢
      • 1970-01-01
      • 2012-12-11
      • 2020-09-01
      • 1970-01-01
      • 2018-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多