我正在尝试通过并行执行其任务而不是串行来加速PHP CLI脚本。这些任务完全相互独立,因此它们开始/结束的顺序并不重要。
这是原始脚本(注意为了清楚起见,所有这些示例都被剥离了):
<?php
$items = range(0, 100);
function do_stuff_with($item) { echo "$item\n"; }
foreach ($items as $item) {
do_stuff_with($item);
}
我设法让$items
与pcntl_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个孩子一次活动。处理这个问题的最佳方法是什么?我尝试过一些东西,但运气不好。
答案 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
那将是每个用户可能无论如何都是你想要的。