如何强制执行最大数量的分叉儿童?

时间:2008-12-03 04:54:09

标签: php c fork process-management

编辑:我已经标记了这个C,希望得到更多回复。它比我特定的语言实现更感兴趣。因此,如果您是C编码器,请将以下PHP视为伪代码,并随时回答用C语言写的答案。

我正在尝试通过并行执行其任务而不是串行来加速PHP CLI脚本。这些任务完全相互独立,因此它们开始/结束的顺序并不重要。

这是原始脚本(注意为了清楚起见,所有这些示例都被剥离了):

<?php

$items = range(0, 100);

function do_stuff_with($item) { echo "$item\n"; }

foreach ($items as $item) {
    do_stuff_with($item);
}

我设法让$itemspcntl_fork()同时发挥作用,如下所示:

<?php

ini_set('max_execution_time', 0); 
ini_set('max_input_time', 0); 
set_time_limit(0);

$items = range(0, 100);

function do_stuff_with($item) { echo "$item\n"; }

$pids = array();
foreach ($items as $item) {
    $pid = pcntl_fork();
    if ($pid == -1) {
        die("couldn't fork()");
    } elseif ($pid > 0) {
        // parent
        $pids[] = $pid;
    } else {
        // child
        do_stuff_with($item);
        exit(0);
    }   
}

foreach ($pids as $pid) {
    pcntl_waitpid($pid, $status);
}

现在我想扩展这个,所以最多有10个孩子一次活动。处理这个问题的最佳方法是什么?我尝试过一些东西,但运气不好。

4 个答案:

答案 0 :(得分:2)

我能想到的最好的事情是将所有任务添加到队列中,启动所需的最大线程数,然后让每个线程从队列中请求任务,执行任务并请求下一个任务。当没有更多的任务要做时,不要忘记让线程终止。

答案 1 :(得分:2)

分叉是一项昂贵的操作。从它的外观来看,你真正想要的是多线程,而不是多处理。不同之处在于线程的重量比进程轻得多,因为线程共享一个虚拟地址空间,但进程具有单独的虚拟地址空间。

我不是一名PHP开发人员,但快速的谷歌搜索显示PHP本身不支持多线程,但有些库可以完成这项工作。

无论如何,一旦你弄清楚如何产生线程,你应该弄清楚要产生多少线程。为此,您需要知道应用程序的瓶颈是什么。瓶颈是CPU,内存还是I / O?您已在评论中指出您是网络绑定的,而网络是一种I / O.

如果你受CPU限制,你只会获得与CPU核心一样多的并行性;任何更多的线程,你只是浪费时间做上下文切换。假设您可以计算出要生成的总线程数,您应该将您的工作划分为多个单元,并让每个线程独立处理一个单元。

如果你受内存限制,那么多线程就无济于事。

由于你是I / O绑定的,弄清楚要生成多少个线程有点棘手。如果所有工作项的处理时间差非常小,则可以通过测量一个工作项所需的时间来估计要生成的线程数。但是,由于网络数据包往往具有高度可变的延迟,因此不太可能出现这种情况。

一个选项是使用线程池 - 您创建了一大堆线程,然后对于要处理的每个项目,您将看到池中是否存在空闲线程。如果有,则让该线程执行工作,然后转到下一个项目。否则,您等待线程可用。选择线程池的大小很重要 - 太大了,而且你在浪费时间做不必要的上下文切换。太少了,而且你经常等待线程。

另一种选择是放弃多线程/多处理,而只是做异步I / O.既然你提到你正在使用单核处理器,这可能是最快的选择。您可以使用socket_select()之类的函数来测试套接字是否有可用数据。如果是,您可以读取数据,否则您将移动到不同的套接字。这需要进行更多的簿记,但是当数据在不同的套接字上可用时,您可以避免等待数据进入一个套接字。

如果您想避开线程和异步I / O并坚持使用多处理,如果每个项目的处理足够昂贵,那么它仍然是值得的。然后你可以这样做工作部门:

$my_process_index = 0;
$pids = array();

// Fork off $max_procs processes
for($i = 0; $i < $max_procs - 1; $i++)
{
  $pid = pcntl_fork();
  if($pid == -1)
  {
    die("couldn't fork()");
  }
  elseif($pid > 0)
  {
    // parent
    $my_process_index++;
    $pids[] = $pid
  }
  else
  {
    // child
    break;
  }
}

// $my_process_index is now an integer in the range [0, $max_procs), unique among all the processes
// Each process will now process 1/$max_procs of the items
for($i = $my_process_index; $i < length($items); $i += $max_procs)
{
  do_stuff_with($items[$i]);
}

if($my_process_index != 0)
{
  exit(0);
}

答案 2 :(得分:2)

没有系统调用来获取子pid列表,但ps可以为您执行此操作。

--ppid开关会列出您处理的所有子项,因此您只需计算ps输出的行数。

或者你可以维护自己的计数器,你将在fork()上递增并递减SIGCHLD信号,假设ppid保持不变以进行分叉处理。

答案 3 :(得分:0)

man 2 setrlimit

那将是每个用户可能无论如何都是你想要的。