การเรียกฟังก์ชันแบบอะซิงโครนัสใน PHP


86

ฉันกำลังทำงานกับเว็บแอปพลิเคชัน PHP และฉันจำเป็นต้องดำเนินการกับเครือข่ายบางอย่างในคำขอเช่นดึงคนจากเซิร์ฟเวอร์ระยะไกลตามคำขอของผู้ใช้

เป็นไปได้ไหมที่จะจำลองพฤติกรรมแบบอะซิงโครนัสใน 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;
?>

การดำเนินการเครือข่ายแต่ละครั้งจะใช้เวลาประมาณ 5 วินาทีในการดำเนินการเพิ่มรวม 15 วินาทีให้กับเวลาตอบสนองของแอปพลิเคชันของฉันเนื่องจากฉันส่งคำขอ 3 ครั้ง

ฟังก์ชัน makeNetworkCall () ทำตามคำขอ HTTP POST

เซิร์ฟเวอร์ระยะไกลเป็น API ของบุคคลที่สามดังนั้นฉันจึงไม่สามารถควบคุมได้

PS: โปรดอย่าตอบข้อเสนอแนะเกี่ยวกับ AJAX หรือสิ่งอื่น ๆ ฉันกำลังมองหาว่าฉันสามารถทำได้ผ่าน PHP หรือไม่อาจใช้นามสกุล C ++ หรืออะไรทำนองนั้น


ลองใช้CURLเพื่อส่งคำขอและดึงข้อมูลบางส่วนจากเว็บ ...
Bogdan Burym

ฉันเชื่อว่าคำตอบวางอยู่ที่นี่: stackoverflow.com/questions/13846192/…บันทึกย่อ: ใช้เธรด
DRAX


คุณสามารถใช้ฟังก์ชันstream_selectของ PHP เพื่อเรียกใช้โค้ดที่ไม่ปิดกั้น ตอบสนองการใช้งานนี้เพื่อสร้างห่วงเหตุการณ์ที่ขับเคลื่อนด้วยคล้ายกับ Node.js
Quinn Comendant

คำตอบ:


20

ปัจจุบันการใช้คิวดีกว่าเธรด (สำหรับผู้ที่ไม่ได้ใช้ Laravel จะมีการใช้งานอื่น ๆ มากมายเช่นนี้ )

แนวคิดพื้นฐานคือสคริปต์ PHP ดั้งเดิมของคุณทำให้งานหรืองานเป็นคิว จากนั้นคุณมีพนักงานจัดคิวทำงานที่อื่นโดยนำงานออกจากคิวและเริ่มประมวลผลโดยไม่ขึ้นกับ PHP ดั้งเดิม

ข้อดีคือ:

  1. ความสามารถในการปรับขยาย - คุณสามารถเพิ่มโหนดผู้ปฏิบัติงานเพื่อให้ทันกับความต้องการ ด้วยวิธีนี้งานจะทำงานแบบขนาน
  2. ความน่าเชื่อถือ - ตัวจัดการคิวสมัยใหม่เช่น RabbitMQ, ZeroMQ, Redis และอื่น ๆ ได้รับการออกแบบให้มีความน่าเชื่อถืออย่างยิ่ง


8

ฉันไม่มีคำตอบโดยตรง แต่คุณอาจต้องการพิจารณาสิ่งเหล่านี้:

  • โครงการหดตัว - https://github.com/recoilphp/recoil
  • นามสกุล LibEvent ของ php? http://www.php.net/manual/th/book.libevent.php
  • กระบวนการฟอร์กhttp://www.php.net/manual/en/function.pcntl-fork.php
  • นายหน้าข้อความกล่าวคือคุณสามารถเริ่มให้คนงานทำการโทร HTTP และเมื่อjobเสร็จแล้วให้แทรกงานใหม่ที่อธิบายถึงงานที่ต้องทำเพื่อประมวลผลเนื้อหาการตอบกลับ HTTP ที่แคชไว้

3

cURL จะเป็นทางเลือกเดียวที่แท้จริงของคุณที่นี่ (ไม่ว่าจะเป็นหรือใช้ซ็อกเก็ตที่ไม่ปิดกั้นและตรรกะที่กำหนดเอง)

ลิงก์นี้จะส่งคุณไปในทิศทางที่ถูกต้อง ไม่มีการประมวลผลแบบอะซิงโครนัสใน PHP แต่หากคุณกำลังพยายามสร้างคำขอเว็บพร้อมกันหลายรายการ cURL multi จะดูแลคุณ


2

ฉันคิดว่าถ้า HTML และ UI อื่น ๆ ต้องการข้อมูลที่ส่งคืนก็จะไม่มีทางแยกออกได้

ฉันเชื่อว่าวิธีเดียวที่จะทำได้ใน PHP คือการบันทึกคำขอในฐานข้อมูลและมีการตรวจสอบ cron ทุก ๆ นาทีหรือใช้บางอย่างเช่นการประมวลผลคิว Gearman หรืออาจจะ exec () กระบวนการบรรทัดคำสั่ง

ในระหว่างนี้หน้า php ของคุณจะต้องสร้าง html หรือ js ที่ทำให้โหลดซ้ำทุก ๆ สองสามวินาทีเพื่อตรวจสอบความคืบหน้าไม่เหมาะ

เพื่อหลีกเลี่ยงปัญหาคุณคาดหวังว่าจะได้รับคำขอที่แตกต่างกันจำนวนเท่าใด คุณสามารถดาวน์โหลดทั้งหมดโดยอัตโนมัติทุกๆชั่วโมงหรือมากกว่านั้นและบันทึกลงในฐานข้อมูลได้หรือไม่?


1

คำถามเก่านี้มีคำตอบใหม่ มีโซลูชัน "async" สำหรับ PHP ในปัจจุบัน (ซึ่งเทียบเท่ากับหลายกระบวนการของ Python ในแง่ที่ว่าพวกเขาสร้างกระบวนการ PHP อิสระใหม่แทนที่จะจัดการในระดับเฟรมเวิร์ก)

สองวิธีที่ฉันได้เห็นคือ

ลองดูสิ!



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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.