问题 一次运行多个exec命令(但等待最后一个完成)


我环顾四周寻找这个,我似乎无法找到任何正在努力做到我的人。

我有通过_POST请求传递给我的函数的信息。基于该数据,我运行一个exec命令来运行一定次数的TCL脚本(使用不同的参数,基于post变量)。现在,我在foreach中有exec所以这需要永远运行(TCL脚本需要15秒左右才能返回,所以如果我需要运行100次,我有点问题)。这是我的代码:

    public function executeAction(){
    //code to parse the _POST variable into an array called devices

    foreach($devices as $devID){
        exec("../path/to/script.tcl -parameter1 ".$device['param1']." -parameter2 ".$device['param2'], $execout[$devID]);
    }
    print_r($execout);
}

显然,这段代码只是删除了大块的摘录,但希望它足以证明我正在尝试做的事情。

我需要立即运行所有的执行程序,我需要等待它们全部完成才能返回。我还需要存储在名为$ execout的数组中的所有脚本的输出。

有任何想法吗?

谢谢!!!


3786
2018-04-02 15:14


起源



答案:


如果你把你的 exec() 在单独的脚本中调用,您可以使用多次并行调用该外部脚本 curl_multi_exec()。这样,您可以在单独的请求中进行所有调用,以便它们可以同时执行。轮询 &$still_running 查看所有请求何时完成,之后您可以收集每个请求的结果。

更新: 这里的 一篇博文 详细描述我所描述的内容。


基于上面链接的博客文章,我将以下示例放在一起。

脚本并行运行:

// waitAndDate.php

<?php
sleep((int)$_GET['time']);
printf('%d secs; %s', $_GET['time'], shell_exec('date'));

脚本并行调用:

// multiExec.php

<?php
$start = microtime(true);

$mh = curl_multi_init();
$handles = array();

// create several requests
for ($i = 0; $i < 5; $i++) {
    $ch = curl_init();

    $rand = rand(5,25); // just making up data to pass to script
    curl_setopt($ch, CURLOPT_URL, "http://domain/waitAndDate.php?time=$rand");
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);

    curl_multi_add_handle($mh, $ch);
    $handles[] = $ch;
}

// execute requests and poll periodically until all have completed
$isRunning = null;
do {
    curl_multi_exec($mh, $isRunning);
    usleep(250000);
} while ($isRunning > 0);

// fetch output of each request
$outputs = array();
for ($i = 0; $i < count($handles); $i++) {
    $outputs[$i] = trim(curl_multi_getcontent($handles[$i]));
    curl_multi_remove_handle($mh, $handles[$i]);
}

curl_multi_close($mh);

print_r($outputs);
printf("Elapsed time: %.2f seconds\n", microtime(true) - $start);

这是我运行几次时收到的一些输出:

Array
(
    [0] => 8 secs; Mon Apr  2 19:01:33 UTC 2012
    [1] => 8 secs; Mon Apr  2 19:01:33 UTC 2012
    [2] => 18 secs; Mon Apr  2 19:01:43 UTC 2012
    [3] => 11 secs; Mon Apr  2 19:01:36 UTC 2012
    [4] => 8 secs; Mon Apr  2 19:01:33 UTC 2012
)
Elapsed time: 18.36 seconds

Array
(
    [0] => 22 secs; Mon Apr  2 19:02:33 UTC 2012
    [1] => 9 secs; Mon Apr  2 19:02:20 UTC 2012
    [2] => 8 secs; Mon Apr  2 19:02:19 UTC 2012
    [3] => 11 secs; Mon Apr  2 19:02:22 UTC 2012
    [4] => 7 secs; Mon Apr  2 19:02:18 UTC 2012
)
Elapsed time: 22.37 seconds

Array
(
    [0] => 5 secs; Mon Apr  2 19:02:40 UTC 2012
    [1] => 18 secs; Mon Apr  2 19:02:53 UTC 2012
    [2] => 7 secs; Mon Apr  2 19:02:42 UTC 2012
    [3] => 9 secs; Mon Apr  2 19:02:44 UTC 2012
    [4] => 9 secs; Mon Apr  2 19:02:44 UTC 2012
)
Elapsed time: 18.35 seconds

希望有所帮助!

一方面注意:确保您的Web服务器可以处理这么多并行请求。如果它按顺序为它们服务,或者只能同时服务很少,那么这种方法很少或根本没有。 :-)


6
2018-04-02 16:13



我很喜欢这个选项!我以前从未使用过cURL,所以我对它非常不熟悉。我已将我的exec语句移动到另一个脚本,我用cURL调用它,就像你链接的博客文章一样。如何将结果返回给调用者?我在新脚本中回显输出,但是当我打印出cURL运行的所有输出时,一切都是空的。谢谢!! - Kevin
@Kevin听起来你有正确的想法。给我一点点,我会尝试掀起一个有效的例子。 - Wiseguy
好的很棒。谢谢! - Kevin
这是否适用于curl post请求?我的意思是这仍然是多线程的吗? - Brana


答案:


如果你把你的 exec() 在单独的脚本中调用,您可以使用多次并行调用该外部脚本 curl_multi_exec()。这样,您可以在单独的请求中进行所有调用,以便它们可以同时执行。轮询 &$still_running 查看所有请求何时完成,之后您可以收集每个请求的结果。

更新: 这里的 一篇博文 详细描述我所描述的内容。


基于上面链接的博客文章,我将以下示例放在一起。

脚本并行运行:

// waitAndDate.php

<?php
sleep((int)$_GET['time']);
printf('%d secs; %s', $_GET['time'], shell_exec('date'));

脚本并行调用:

// multiExec.php

<?php
$start = microtime(true);

$mh = curl_multi_init();
$handles = array();

// create several requests
for ($i = 0; $i < 5; $i++) {
    $ch = curl_init();

    $rand = rand(5,25); // just making up data to pass to script
    curl_setopt($ch, CURLOPT_URL, "http://domain/waitAndDate.php?time=$rand");
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);

    curl_multi_add_handle($mh, $ch);
    $handles[] = $ch;
}

// execute requests and poll periodically until all have completed
$isRunning = null;
do {
    curl_multi_exec($mh, $isRunning);
    usleep(250000);
} while ($isRunning > 0);

// fetch output of each request
$outputs = array();
for ($i = 0; $i < count($handles); $i++) {
    $outputs[$i] = trim(curl_multi_getcontent($handles[$i]));
    curl_multi_remove_handle($mh, $handles[$i]);
}

curl_multi_close($mh);

print_r($outputs);
printf("Elapsed time: %.2f seconds\n", microtime(true) - $start);

这是我运行几次时收到的一些输出:

Array
(
    [0] => 8 secs; Mon Apr  2 19:01:33 UTC 2012
    [1] => 8 secs; Mon Apr  2 19:01:33 UTC 2012
    [2] => 18 secs; Mon Apr  2 19:01:43 UTC 2012
    [3] => 11 secs; Mon Apr  2 19:01:36 UTC 2012
    [4] => 8 secs; Mon Apr  2 19:01:33 UTC 2012
)
Elapsed time: 18.36 seconds

Array
(
    [0] => 22 secs; Mon Apr  2 19:02:33 UTC 2012
    [1] => 9 secs; Mon Apr  2 19:02:20 UTC 2012
    [2] => 8 secs; Mon Apr  2 19:02:19 UTC 2012
    [3] => 11 secs; Mon Apr  2 19:02:22 UTC 2012
    [4] => 7 secs; Mon Apr  2 19:02:18 UTC 2012
)
Elapsed time: 22.37 seconds

Array
(
    [0] => 5 secs; Mon Apr  2 19:02:40 UTC 2012
    [1] => 18 secs; Mon Apr  2 19:02:53 UTC 2012
    [2] => 7 secs; Mon Apr  2 19:02:42 UTC 2012
    [3] => 9 secs; Mon Apr  2 19:02:44 UTC 2012
    [4] => 9 secs; Mon Apr  2 19:02:44 UTC 2012
)
Elapsed time: 18.35 seconds

希望有所帮助!

一方面注意:确保您的Web服务器可以处理这么多并行请求。如果它按顺序为它们服务,或者只能同时服务很少,那么这种方法很少或根本没有。 :-)


6
2018-04-02 16:13



我很喜欢这个选项!我以前从未使用过cURL,所以我对它非常不熟悉。我已将我的exec语句移动到另一个脚本,我用cURL调用它,就像你链接的博客文章一样。如何将结果返回给调用者?我在新脚本中回显输出,但是当我打印出cURL运行的所有输出时,一切都是空的。谢谢!! - Kevin
@Kevin听起来你有正确的想法。给我一点点,我会尝试掀起一个有效的例子。 - Wiseguy
好的很棒。谢谢! - Kevin
这是否适用于curl post请求?我的意思是这仍然是多线程的吗? - Brana


PHP的exec函数将始终等待执行的响应。但是,您可以将进程的stdout和stderror发送到/ dev / null(在unix上)并使这些脚本几乎立即执行。这可以通过添加..

 '> /dev/null 2>&1 &'

到执行字符串的末尾。

但!这意味着他们将分开并独立完成处理。可能值得让他们在某处写回复。你可以创建一个监听器来挑选它并处理它。


5
2018-04-02 15:49



嗯,也许我误解了。我需要脚本的所有输出(它只是将输出写入终端,而不是写入文件)。我不会用这种方法放松输出吗? - Kevin
这是对的,虽然我认为你很难让每个进程异步运行并让它们都将结果反馈到一个脚本中。它当然可以使用过程分叉 php.net/manual/en/function.pcntl-fork.php 但不容易,也不支持Windows。也许设置一个简单的消息队列系统来处理这个问题将是一个更好的解决方案。查看ZeroMQ,或者RabbitMQ。 - Lee Davis


引用PHP文档:

注意:

如果使用此功能启动程序,为了使其在后台继续运行,必须将程序的输出重定向到文件或其他输出流。如果不这样做将导致PHP挂起,直到程序执行结束。

因此,如果您将输出重定向到文件中,则可以在后台执行:

exec("../path/to/script.tcl -parameter1 ".$device['param1']." -parameter2 ".$device['param2']." > outputfile.txt", $execout[$devID]);

但是如果你想等待所有的高管在继续之前完成,你必须从外部脚本进行回调。也许是这样的:

exec("../path/to/script.tcl -parameter1 ".$device['param1']." -parameter2 ".$device['param2']." > ../path/to/outputfile.txt; ".PHP_BINDIR.DIRECTORY_SEPARATOR."php ../path/to/callback.php", $execout[$devID]);

像这样,你的callback.php脚本将在script.tcl的每个exec之后调用。

也许你可以用这些技巧做点什么。


1
2018-04-02 15:49



那么我会有多个output.txt文件吗?或者我只是将脚本附加到同一个文件?这似乎是多个高管最终可能会写入同一个文件。 - Kevin


您需要稍微修改一下脚本

  1. 将发布数据保存到会话中
  2. 执行,将结果保存到会话
  3. 使用JavaScript使用重定向
  4. exec返回后的Echo重定向命令,使用相同的URL但添加增量索引,例如?index = 99
  5. 当索引到达结束时,显示整个结果

1
2018-04-02 16:00



这不会依次/串行而不是并行运行执行程序吗? - Kevin
嗯,但是在网络编程中,如果不确切知道你的执行人员将要运行多长时间,你就不能等待太久。并且在收集结果时,您可能会遇到竞争条件 - Superbiji


查看libputil库中的ExecFuture andFutureIterator:

https://secure.phabricator.com/book/libphutil/class/ExecFuture/

它通过非常好的语法完全满足您的需求:

$futures = array();
foreach ($files as $file) {
  $futures[$file] = new ExecFuture("gzip %s", $file);
}
foreach (new FutureIterator($futures) as $file => $future) {
  list($err, $stdout, $stderr) = $future->resolve();
  if (!$err) {
    echo "Compressed {$file}...\n";
  } else {
    echo "Failed to compress {$file}!\n";
  }
}

0
2017-08-02 14:26