Gọi hàm không đồng bộ trong PHP


86

Tôi đang làm việc trên một ứng dụng web PHP và tôi cần thực hiện một số hoạt động mạng trong yêu cầu như tìm nạp ai đó từ máy chủ từ xa dựa trên yêu cầu của người dùng.

Có thể mô phỏng hành vi không đồng bộ trong PHP không khi tôi phải truyền một số dữ liệu vào một hàm và cũng cần đầu ra từ nó.

Mã của tôi như sau:

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

Mỗi thao tác mạng mất khoảng 5 giây để hoàn thành, cộng tổng cộng 15 giây vào thời gian phản hồi của ứng dụng của tôi với điều kiện tôi thực hiện 3 yêu cầu.

Hàm makeNetworkCall () chỉ thực hiện một yêu cầu HTTP POST.

Máy chủ từ xa là API của bên thứ 3 nên tôi không có bất kỳ quyền kiểm soát nào ở đó.

Tái bút: Vui lòng không trả lời khi đưa ra gợi ý về AJAX hoặc những thứ khác. Tôi hiện đang tìm hiểu xem liệu tôi có thể làm điều này thông qua PHP hay không với phần mở rộng C ++ hoặc thứ gì đó tương tự.


Hãy thử sử dụng CURLcác yêu cầu lửa và lấy một số dữ liệu từ trang web ...
Bogdan Burym

Tôi tin rằng câu trả lời nằm ở đây: stackoverflow.com/questions/13846192/… Lưu ý nhanh: sử dụng luồng
DRAX


Bạn có thể sử dụng hàm stream_select của PHP để chạy mã không chặn. Phản ứng dụng này để tạo ra một vòng lặp hướng sự kiện tương tự như Node.js .
Quinn Comendant

Câu trả lời:


20

Ngày nay, tốt hơn là sử dụng hàng đợi hơn là luồng (đối với những người không sử dụng Laravel thì có rất nhiều triển khai khác như thế này ).

Ý tưởng cơ bản là, tập lệnh PHP ban đầu của bạn đặt các nhiệm vụ hoặc công việc vào một hàng đợi. Sau đó, bạn có nhân viên công việc hàng đợi đang chạy ở nơi khác, lấy công việc ra khỏi hàng đợi và bắt đầu xử lý chúng độc lập với PHP gốc.

Những ưu điểm là:

  1. Khả năng mở rộng - bạn chỉ có thể thêm các nút công nhân để theo kịp nhu cầu. Bằng cách này, các tác vụ được chạy song song.
  2. Độ tin cậy - các trình quản lý hàng đợi hiện đại như RabbitMQ, ZeroMQ, Redis, v.v., được tạo ra để trở nên cực kỳ đáng tin cậy.


8

Tôi không có câu trả lời trực tiếp, nhưng bạn có thể muốn xem xét những điều sau:


3

cURL sẽ là sự lựa chọn thực sự duy nhất của bạn ở đây (hoặc đó, hoặc sử dụng các ổ cắm không chặn và một số logic tùy chỉnh).

Liên kết này sẽ đưa bạn đi đúng hướng. Không có quá trình xử lý không đồng bộ trong PHP, nhưng nếu bạn đang cố gắng thực hiện nhiều yêu cầu web đồng thời, cURL multi sẽ giải quyết việc đó cho bạn.


2

Tôi nghĩ nếu HTML và các nội dung giao diện người dùng khác cần dữ liệu được trả về thì sẽ không có cách nào để đồng bộ hóa nó.

Tôi tin rằng cách duy nhất để làm điều này trong PHP là đăng nhập một yêu cầu trong cơ sở dữ liệu và kiểm tra cron mỗi phút hoặc sử dụng một cái gì đó như xử lý hàng đợi Gearman hoặc có thể thực hiện () một quy trình dòng lệnh

Trong khi chờ đợi, trang php của bạn sẽ phải tạo một số html hoặc js khiến nó tải lại sau mỗi vài giây để kiểm tra tiến trình, không phải là lý tưởng.

Để vượt qua vấn đề, bạn đang mong đợi bao nhiêu yêu cầu khác nhau? Bạn có thể tải xuống tất cả chúng tự động mỗi giờ hoặc lâu hơn và lưu vào cơ sở dữ liệu không?



0

Tôi nghĩ rằng một số mã về giải pháp cURL là cần thiết ở đây, vì vậy tôi sẽ chia sẻ của tôi (nó được viết kết hợp nhiều nguồn dưới dạng Hướng dẫn sử dụng PHP và nhận xét).

Nó thực hiện một số yêu cầu HTTP song song (các miền trong $aURLs) và in các phản hồi sau khi hoàn thành mỗi một (và lưu trữ chúng $donecho các mục đích sử dụng có thể khác).

Mã dài hơn mức cần thiết vì phần in thời gian thực và quá nhiều nhận xét, nhưng vui lòng chỉnh sửa câu trả lời để cải thiện nó:

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

Một cách là sử dụng pcntl_fork()trong một hàm đệ quy.

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

Có một điều pcntl_fork()là khi chạy script bằng Apache, nó không hoạt động (nó không được Apache hỗ trợ). Vì vậy, một cách để giải quyết vấn đề đó là chạy tập lệnh bằng php cli, như: exec('php fork.php',$output);từ một tệp khác. Để thực hiện việc này, bạn sẽ có hai tệp: một tệp được Apache tải và một exec()tệp chạy từ bên trong tệp được Apache tải như sau:

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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.