PHP 中的异步函数调用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14236296/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 06:55:06  来源:igfitidea点击:

Asynchronous Function Call in PHP

phpasynchronousnetwork-programming

提问by Hardeep Singh

I am working on an a PHP web application and i need to perform some network operations in the request like fetching someone from remote server based on user's request.

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

Is it possible to simulate asynchronous behavior in PHP given that i have to pass some data to a function and also need output from it.

是否可以在 PHP 中模拟异步行为,因为我必须将一些数据传递给一个函数并且还需要它的输出。

My code is like:

我的代码是这样的:

<?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;
?>

Each network operation takes around 5 seconds to complete adding a total of 15 seconds to the response time of my application given i make 3 requests.

如果我发出 3 个请求,每个网络操作大约需要 5 秒才能完成,我的应用程序的响应时间总共增加了 15 秒。

The makeNetworkCall() function just do a HTTP POST request.

makeNetworkCall() 函数只是做一个 HTTP POST 请求。

The remote server is an 3rd party API so i don't have any control over there.

远程服务器是第 3 方 API,所以我无法控制那里。

PS: Please do not answer giving suggestions about AJAX or Other things. I am currently looking if i can do this through PHP may be with an C++ extension or something like that.

PS:请不要回答就 AJAX 或其他事情提出建议。我目前正在寻找我是否可以通过 PHP 来做到这一点,可能是 C++ 扩展或类似的东西。

回答by aljo f

Nowadays, it's better to use queuesthan threads (for those who don't use Laravel there are tons of other implementations out there like this).

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

The basic idea is, your original PHP script puts tasks or jobs into a queue. Then you have queue job workers running elsewhere, taking jobs out of the queue and starts processing them independently of the original PHP.

基本思想是,您的原始 PHP 脚本将任务或作业放入队列中。然后您让队列作业工作程序在其他地方运行,从队列中取出作业并开始独立于原始 PHP 处理它们。

The advantages are:

优点是:

  1. Scalability - you can just add worker nodes to keep up with demand. In this way, tasks are run in parallel.
  2. Reliability - modern queue managers such as RabbitMQ, ZeroMQ, Redis, etc, are made to be extremely reliable.
  1. 可扩展性 - 您只需添加工作节点即可满足需求。这样,任务并行运行。
  2. 可靠性——现代队列管理器,如 RabbitMQ、ZeroMQ、Redis 等,都非常可靠。

回答by Sebastiaan Hilbers

I dont have a direct answer, but you might want to look into these things:

我没有直接的答案,但你可能想看看这些东西:

回答by Colin M

cURL is going to be your only real choice here (either that, or using non-blocking sockets and some custom logic).

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

This linkshould send you in the right direction. There is no asynchronous processing in PHP, but if you're trying to make multiple simultaneous web requests, cURL multi will take care of that for you.

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

回答by CodeMonkey

I think if the HTML and other UI stuff needs the data returned then there is not going to be a way to async it.

我认为如果 HTML 和其他 UI 内容需要返回的数据,那么将没有办法异步它。

I believe the only way to do this in PHP would be to log a request in a database and have a cron check every minute, or use something like Gearman queue processing, or maybe exec() a command line process

我相信在 PHP 中做到这一点的唯一方法是在数据库中记录一个请求并每分钟进行一次 cron 检查,或者使用类似 Gearman 队列处理的东西,或者 exec() 一个命令行进程

In the meantime you php page would have to generate some html or js that makes it reload every few seconds to check on progress, not ideal.

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

To sidestep the issue, how many different requests are you expecting? Could you download them all automatically every hour or so and save to a database?

为了回避这个问题,您期望有多少不同的请求?你能每隔一小时左右自动下载它们并保存到数据库吗?

回答by Erik van Velzen

There is also http v2 which is a wrapper for curl. Can be installed via pecl.

还有 http v2,它是 curl 的包装器。可以通过pecl安装。

http://devel-m6w6.rhcloud.com/mdref/http/

http://devel-m6w6.rhcloud.com/mdref/http/

回答by Leopoldo Sanczyk

I think some code about the cURL solution is needed here, so I will share mine (it was written mixing several sources as the PHP Manual and comments).

我认为这里需要一些关于 cURL 解决方案的代码,所以我将分享我的代码(它是混合了几个来源作为 PHP 手册和评论编写的)。

It does some parallel HTTP requests (domains in $aURLs) and print the responses once each one is completed (and stored them in $donefor other possible uses).

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

The code is longer than needed because the realtime print part and the excess of comments, but feel free to edit the answer to improve it:

由于实时打印部分和过多的注释,代码比需要的要长,但请随时编辑答案以改进它:

<?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
?>

回答by JVE999

One way is to use pcntl_fork()in a recursive function.

一种方法是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);

One thing about pcntl_fork()is that when running the script by way of Apache, it doesn't work (it's not supported by Apache). So, one way to resolve that issue is to run the script using the php cli, like: exec('php fork.php',$output);from another file. To do this you'll have two files: one that's loaded by Apache and one that's run with exec()from inside the file loaded by Apache like this:

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

apacheLoadedFile.php

apacheLoadedFile.php

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

fork.php

叉子.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);