Panggilan Fungsi Asinkron di PHP

86

Saya sedang mengerjakan aplikasi web PHP dan saya perlu melakukan beberapa operasi jaringan dalam permintaan seperti mengambil seseorang dari server jarak jauh berdasarkan permintaan pengguna.

Apakah mungkin untuk mensimulasikan perilaku asynchronous dalam PHP mengingat bahwa saya harus meneruskan beberapa data ke suatu fungsi dan juga memerlukan keluaran darinya.

Kode saya seperti:

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

Setiap operasi jaringan membutuhkan waktu sekitar 5 detik untuk menyelesaikan penambahan total 15 detik ke waktu respons aplikasi saya karena saya membuat 3 permintaan.

Fungsi makeNetworkCall () hanya melakukan permintaan HTTP POST.

Server jarak jauh adalah API pihak ketiga jadi saya tidak memiliki kendali apa pun di sana.

PS: Mohon tidak menjawab memberikan saran tentang AJAX atau hal-hal lain. Saat ini saya sedang mencari apakah saya dapat melakukan ini melalui PHP mungkin dengan ekstensi C ++ atau sesuatu seperti itu.

Hardeep Singh
sumber
Coba gunakan CURLuntuk mengaktifkan permintaan dan mengambil beberapa data dari web ...
Bogdan Burym
Saya percaya bahwa jawabannya ada di sini: stackoverflow.com/questions/13846192/… Catatan
singkat
Anda dapat menggunakan fungsi stream_select PHP untuk menjalankan kode non-pemblokiran. Bereaksi menggunakan ini untuk membuat lingkaran-event serupa dengan node.js .
Quinn Comendant

Jawaban:

20

Saat ini, lebih baik menggunakan antrian daripada utas (bagi mereka yang tidak menggunakan Laravel, ada banyak implementasi lain di luar sana seperti ini ).

Ide dasarnya adalah, skrip PHP asli Anda menempatkan tugas atau pekerjaan ke dalam antrian. Kemudian Anda memiliki pekerja pekerjaan antrian yang berjalan di tempat lain, mengambil pekerjaan dari antrian dan mulai memprosesnya secara independen dari PHP asli.

Keunggulannya adalah:

  1. Skalabilitas - Anda cukup menambahkan node pekerja untuk memenuhi permintaan. Dengan cara ini, tugas dijalankan secara paralel.
  2. Keandalan - manajer antrian modern seperti RabbitMQ, ZeroMQ, Redis, dll, dibuat sangat andal.
aljo f
sumber
8

Saya tidak memiliki jawaban langsung, tetapi Anda mungkin ingin melihat hal-hal ini:

Sebastiaan Hilbers
sumber
3

cURL akan menjadi satu-satunya pilihan nyata Anda di sini (baik itu, atau menggunakan soket non-pemblokiran dan beberapa logika khusus).

Tautan ini akan mengarahkan Anda ke arah yang benar. Tidak ada pemrosesan asinkron dalam PHP, tetapi jika Anda mencoba membuat beberapa permintaan web secara bersamaan, cURL multi akan mengurusnya untuk Anda.

Colin M
sumber
2

Saya pikir jika HTML dan UI lainnya membutuhkan data yang dikembalikan maka tidak akan ada cara untuk melakukan asinkronisasi.

Saya percaya satu-satunya cara untuk melakukan ini di PHP adalah dengan mencatat permintaan dalam database dan melakukan pemeriksaan cron setiap menit, atau menggunakan sesuatu seperti pemrosesan antrian Gearman, atau mungkin exec () proses baris perintah

Sementara itu, halaman php Anda harus membuat beberapa html atau js yang membuatnya dimuat ulang setiap beberapa detik untuk memeriksa kemajuan, tidak ideal.

Untuk menghindari masalah, berapa banyak permintaan berbeda yang Anda harapkan? Bisakah Anda mendownload semuanya secara otomatis setiap satu jam atau lebih dan menyimpannya ke database?

CodeMonkey
sumber
1

Pertanyaan lama ini punya jawaban baru. Ada beberapa solusi "async" untuk PHP hari ini (yang setara dengan multiproses Python dalam arti mereka menelurkan proses PHP independen baru daripada mengelolanya di tingkat kerangka kerja)

Dua solusi yang telah saya lihat adalah

Cobalah!

Jimmy
sumber
0

Saya pikir beberapa kode tentang solusi cURL diperlukan di sini, jadi saya akan membagikan milik saya (ditulis mencampurkan beberapa sumber sebagai Manual PHP dan komentar).

Itu melakukan beberapa permintaan HTTP paralel (domain di $aURLs) dan mencetak tanggapan setelah masing-masing selesai (dan menyimpannya di$done untuk kemungkinan penggunaan lain).

Kode lebih panjang dari yang dibutuhkan karena bagian cetak waktu nyata dan kelebihan komentar, tetapi jangan ragu untuk mengedit jawaban untuk memperbaikinya:

<?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
?>
Leopoldo Sanczyk
sumber
0

Salah satu caranya adalah dengan menggunakan pcntl_fork()fungsi rekursif.

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

Satu hal tentang pcntl_fork()adalah bahwa ketika menjalankan skrip dengan Apache, itu tidak berfungsi (tidak didukung oleh Apache). Jadi, salah satu cara untuk mengatasi masalah itu adalah dengan menjalankan skrip menggunakan php cli, seperti: exec('php fork.php',$output);dari file lain. Untuk melakukan ini, Anda akan memiliki dua file: satu yang dimuat oleh Apache dan satu lagi yang dijalankan denganexec() dari dalam file yang dimuat oleh Apache seperti ini:

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