PHP中的异步函数调用


86

我正在使用PHP Web应用程序,并且需要在请求中执行一些网络操作,例如根据用户的请求从远程服务器中获取某人。

假设我必须将一些数据传递给函数并且还需要从函数输出,是否有可能在PHP中模拟异步行为。

我的代码是这样的:

<?php

     $data1 = processGETandPOST();
     $data2 = processGETandPOST();
     $data3 = processGETandPOST();

     $response1 = makeNetworkCall($data1);
     $response2 = makeNetworkCall($data2);
     $response3 = makeNetworkCall($data3);

     processNetworkResponse($response1);
     processNetworkResponse($response2);
     processNetworkResponse($response3);

     /*HTML and OTHER UI STUFF HERE*/

     exit;
?>

给定3个请求,每个网络操作大约需要5秒钟才能完成,因此总共增加了15秒钟的响应时间。

makeNetworkCall()函数仅执行HTTP POST请求。

远程服务器是一个第三方API,因此我对那里没有任何控制权。

PS:请不要回答有关AJAX或其他问题的建议。我目前正在寻找是否可以通过PHP进行此操作,它可能带有C ++扩展名或类似名称。


尝试使用CURL触发请求并从Web上获取一些数据...
Bogdan Burym

我相信答案就在这里:stackoverflow.com/questions/13846192/…快速说明:使用线程
DRAX


您可以使用PHP的stream_select函数来运行非阻塞代码。React使用它来创建类似于node.js事件驱动的循环
2015年

Answers:


20

如今,它是更好地使用队列比线程(对于那些谁不使用Laravel有吨其他实现在那里像这样)。

基本思想是,您原始的PHP脚本会将任务或作业放入队列。然后,您将有队列作业人员在其他地方运行,将作业从队列中移出并开始独立于原始PHP进行处理。

优点是:

  1. 可伸缩性-您只需添加工作节点即可满足需求。这样,任务可以并行运行。
  2. 可靠性-现代化的队列管理器(例如RabbitMQ,ZeroMQ,Redis等)都非常可靠。



3

在这里,cURL将是您唯一的选择(或者使用非阻塞套接字和一些自定义逻辑)。

该链接应该向您发送正确的方向。PHP中没有异步处理,但是如果您尝试同时发出多个Web请求,则cURL multi会为您解决这一问题。


2

我认为,如果HTML和其他UI东西需要返回的数据,那么将没有一种异步的方法。

我相信在PHP中执行此操作的唯一方法是将请求记录到数据库中并每分钟进行一次cron检查,或者使用诸如Gearman队列处理之类的方法,或者使用exec()命令行过程

同时,您的php页面必须生成一些html或js,使其每隔几秒钟重新加载一次,以检查进度,这并不理想。

要回避这个问题,您期望多少个不同的请求?您能否每小时大约自动下载所有文件并保存到数据库?



0

我认为这里需要一些有关cURL解决方案的代码,所以我将与我分享(它是混合使用多个源代码(如PHP手册和注释)编写的)。

它会执行一些并行的HTTP请求(位于中的域$aURLs),并在每个请求完成后打印响应(并将响应存储在其中以$done用于其他可能的用途)。

该代码比所需的时间更长,因为实时打印部分和多余的注释,但是可以随时编辑答案以改进它:

<?php
/* Strategies to avoid output buffering, ignore the block if you don't want to print the responses before every cURL is completed */
ini_set('output_buffering', 'off'); // Turn off output buffering
ini_set('zlib.output_compression', false); // Turn off PHP output compression       
//Flush (send) the output buffer and turn off output buffering
ob_end_flush(); while (@ob_end_flush());        
apache_setenv('no-gzip', true); //prevent apache from buffering it for deflate/gzip
ini_set('zlib.output_compression', false);
header("Content-type: text/plain"); //Remove to use HTML
ini_set('implicit_flush', true); // Implicitly flush the buffer(s)
ob_implicit_flush(true);
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
$string=''; for($i=0;$i<1000;++$i){$string.=' ';} output($string); //Safari and Internet Explorer have an internal 1K buffer.
//Here starts the program output

function output($string){
    ob_start();
    echo $string;
    if(ob_get_level()>0) ob_flush();
    ob_end_clean();  // clears buffer and closes buffering
    flush();
}

function multiprint($aCurlHandles,$print=true){
    global $done;
    // iterate through the handles and get your content
    foreach($aCurlHandles as $url=>$ch){
        if(!isset($done[$url])){ //only check for unready responses
            $html = curl_multi_getcontent($ch); //get the content           
            if($html){
                $done[$url]=$html;
                if($print) output("$html".PHP_EOL);
            }           
        }
    }
};

function full_curl_multi_exec($mh, &$still_running) {
    do {
      $rv = curl_multi_exec($mh, $still_running); //execute the handles 
    } while ($rv == CURLM_CALL_MULTI_PERFORM); //CURLM_CALL_MULTI_PERFORM means you should call curl_multi_exec() again because there is still data available for processing
    return $rv;
} 

set_time_limit(60); //Max execution time 1 minute

$aURLs = array("http://domain/script1.php","http://domain/script2.php");  // array of URLs

$done=array();  //Responses of each URL

    //Initialization
    $aCurlHandles = array(); // create an array for the individual curl handles
    $mh = curl_multi_init(); // init the curl Multi and returns a new cURL multi handle
    foreach ($aURLs as $id=>$url) { //add the handles for each url        
        $ch = curl_init(); // init curl, and then setup your options
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // returns the result - very important
        curl_setopt($ch, CURLOPT_HEADER, 0); // no headers in the output
        $aCurlHandles[$url] = $ch;
        curl_multi_add_handle($mh,$ch);
    }

    //Process
    $active = null; //the number of individual handles it is currently working with
    $mrc=full_curl_multi_exec($mh, $active); 
    //As long as there are active connections and everything looks OK…
    while($active && $mrc == CURLM_OK) { //CURLM_OK means is that there is more data available, but it hasn't arrived yet.  
        // Wait for activity on any curl-connection and if the network socket has some data…
        if($descriptions=curl_multi_select($mh,1) != -1) {//If waiting for activity on any curl_multi connection has no failures (1 second timeout)     
            usleep(500); //Adjust this wait to your needs               
            //Process the data for as long as the system tells us to keep getting it
            $mrc=full_curl_multi_exec($mh, $active);        
            //output("Still active processes: $active".PHP_EOL);        
            //Printing each response once it is ready
            multiprint($aCurlHandles);  
        }
    }

    //Printing all the responses at the end
    //multiprint($aCurlHandles,false);      

    //Finalize
    foreach ($aCurlHandles as $url=>$ch) {
        curl_multi_remove_handle($mh, $ch); // remove the handle (assuming  you are done with it);
    }
    curl_multi_close($mh); // close the curl multi handler
?>

0

一种方法是pcntl_fork()在递归函数中使用。

function networkCall(){
  $data = processGETandPOST();
  $response = makeNetworkCall($data);
  processNetworkResponse($response);
  return true;
}

function runAsync($times){
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    $times -= 1;
    if($times>0)
      runAsync($times);
    pcntl_wait($status); //Protect against Zombie children
  } else {
    // we are the child
    networkCall();
    posix_kill(getmypid(), SIGKILL);
  }
}

runAsync(3);

关于一件事pcntl_fork()是,当通过Apache运行脚本时,它不起作用(Apache不支持)。因此,解决该问题的一种方法是使用php cli运行脚本,例如:exec('php fork.php',$output);从另一个文件。为此,您将有两个文件:一个是由Apache加载的文件,另一个是exec()从Apache加载的文件内部运行的文件,如下所示:

apacheLoadedFile.php

exec('php fork.php',$output);

fork.php

function networkCall(){
  $data = processGETandPOST();
  $response = makeNetworkCall($data);
  processNetworkResponse($response);
  return true;
}

function runAsync($times){
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    $times -= 1;
    if($times>0)
      runAsync($times);
    pcntl_wait($status); //Protect against Zombie children
  } else {
    // we are the child
    networkCall();
    posix_kill(getmypid(), SIGKILL);
  }
}

runAsync(3);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.