同时运行多个exec命令(但请等待最后一个命令完成)


Run multiple exec commands at once (But wait for the last one to finish)

我四处寻找,似乎找不到任何人试图做我自己。

我有通过_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);
}

显然,这段代码只是一段去掉大块的摘录,但希望它足以证明我正在尝试做什么

我需要一次运行所有的exec,我需要等待它们全部完成后再返回。我还需要存储在名为$execout的数组中的所有脚本的输出。

有什么想法吗?

谢谢!!!

如果将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服务器能够处理这么多并行请求。如果它按顺序发球,或者只能同时发球很少,这种方法对你几乎没有好处。:-)

PHP的exec函数将始终等待执行的响应。但是,您可以发送stdout&进程的stderror设置为/dev/null(在unix上),并几乎立即执行所有脚本。这可以通过添加..来完成。。

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

到执行字符串的末尾。

但是!这意味着他们将独立完成处理。让他们在某个地方写一个回复可能是值得的。您可以创建一个监听器来接收并处理它。

引用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.的每次执行之后被调用

也许你可以用这些把戏做点什么。

您需要修改您的脚本一点

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

查看libputil库中的ExecFuture和FutureIterator:

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";
  }
}