天天看點

如何實作異步執行

浏覽器和伺服器之間隻一種面向無連接配接的HTTP協定進行通訊的,面向無連接配接的程式的特點是用戶端請求服務端,服務端根據請求輸出相應的程式,不能保持持久連接配接。

這樣就出現了一個問題,一個用戶端的相應服務端可能執行1秒也有可能執行1分鐘,這樣浏覽器就會一直處于等待狀态,如果程式執行緩慢,使用者可能就沒耐心關掉了浏覽器。

而有的時候我們不需要關心程式執行的結果,沒有必要這樣浪費時間和耐心等待,那我們就要想出辦法讓程式不收等待在背景靜默執行。

比如現在有一個場景,給1000個使用者發送一封推薦郵件,使用者輸入或者導入郵件賬号了送出伺服器執行發送。

<?php

$count=count($emailarr);

for($i=0;$i<$count;$i++)

{

  sendmail(.....);//發送郵件

}

?>

這段代碼使用者體驗極差,也無法實際運用,首先發送這麼多郵件會産生伺服器運作逾時,其實漫長的使用者等待時間會讓使用者對系統産品懷疑和失去信心。但是使用者不需要等待到1000封郵件都發送完畢了才送出發送成功,我們完全可以送出背景後直接給使用者提示發送成功,然後讓背景程式靜默依次發送。

這個時候我們就需要“異步執行”技術來執行代碼,異步執行的特點是背景靜默執行,使用者無需等待代碼的執行結果,使用異步執行的好處:

1.擺脫了應用程式對單個任務的依賴性

2.提高了程式的執行效率

3.提高了程式的擴充性

4.在一定場景提高了使用者體驗

5.因為PHP不支援多線程,使用異步調用的請求多個HTTP的方式達到了程式并行執行效果,但是注意的是請求的HTTP過多的話,會大大加大了系統的開銷

是以對于耗時的操作适合異步執行,伺服器接收到請求後,處理完用戶端需要的資料就傳回,再異步在伺服器執行耗時的操作。

解決方法

fsockopen支援socket程式設計,可以使用fsockopen實作郵件發送等socket程式等等,使用fcockopen需要自己手動拼接出header部分

可以參考: http://cn.php.net/fsockopen/

使用示例如下:

  1. $fp = fsockopen("www.34ways.com", 80, $errno, $errstr, 30);

  2. if (!$fp) {

  3. echo "$errstr ($errno)<br />\n";

  4. } else {

  5. $out = "GET /index.php / HTTP/1.1\r\n";

  6. $out .= "Host: www.34ways.com\r\n";

  7. $out .= "Connection: Close\r\n\r\n";

  8. fwrite($fp, $out);

  9. /*忽略執行結果

  10. while (!feof($fp)) {

  11. echo fgets($fp, 128);

  12. }*/

  13. fclose($fp);

  14. }

是以總結來說,fscokopen()函數應該可以滿足您的要求。可以嘗試一下。

fscokopen的問題和popen一樣,并發非常多時會産生很多子程序,當達到apache的連接配接限制數時,就會挂掉,我問題已經說了這種情況。

PHP本身沒有多線程的東西,但可以曲線的辦法來造就出同樣的效果,比如多程序的方式來達到異步調用,隻限于指令模式。還有一種更簡單的方式,可用于 Web 程式中,那就是用fsockopen()、fwrite() 來請求一個 URL 而無需等待傳回,如果你在那個被請求的頁面中做些事情就相當于異步了。

關鍵代碼如下:

複制

  1. $fp=fsockopen('localhost',80,$errno,$errstr,5);
  2. if(!$fp){
  3. echo "$errstr ($errno)<br />\n";
  4. }
  5. fputs($fp,"GET another_page.php?flag=1\r\n");
  6. fclose($fp);

上面的代碼向頁面 another_page.php 發送完請求就不管了,用不着等待請求頁面的響應資料,利用這一點就可以在被請求的頁面 another_page.php 中異步的做些事情了。

比如,一個很切實的應用,某個 Blog 在每 Post 了一篇新日志後需要給所有它的訂閱者發個郵件通知。如果按照通常的方式就是:

日志寫完 -> 點送出按鈕 -> 日志插入到資料庫 -> 發送郵件通知 ->

告知撰寫者釋出成功

那麼作者在點送出按鈕到看到成功提示之間可能會等待很常時間,基本是在等郵件發送的過程,比如連接配接郵件服務異常、或器緩慢或是訂閱者太多。而實際上是不管郵件發送成功與否,保證日志儲存成功基本可接受的,是以等待郵件發送的過程是很不經濟的,這個過程可異步來執行,并且郵件發送的結果不太關心或以日志形式記錄備查。

改進後的流程就是:

日志寫完 -> 點送出按鈕 -> 日志插入到資料庫 --->

告知撰寫者釋出成功

└ 發送郵件通知 -> [記下日志]

用個實際的程式來測試一下,有兩個 php,分别是 write.php 和 sendmail.php,在 sendmail.php 用 sleep(seconds) 來模拟程式執行使用時間。

write.php,執行耗時 1 秒

  1. <?php

  2. /**

    * 耗時異步操作

    * @param $url 模闆 子產品名稱/控制器/方法?參數名=參數值

    * 嚴格注意url的格式和fsockopen,fwrite,fclose的格式

    * */

  3. function asyn_sendmail() {

  4. $fp=fsockopen('localhost',80,$errno,$errstr,5);

  5. if(!$fp){

  6. echo "$errstr ($errno)<br />\n";

  7. }

  8. sleep(1);

  9. $out = "GET /sendmail.php?param=1 HTTP/1.1\r\n";

    $out .= "Host:localhost\r\n";

    $out .= "Connection: Close\r\n\r\n";

    fwrite($fp, $out);

  10. fclose($fp);

  11. }

  12. echo time().'<br>';

  13. echo 'call asyn_sendmail<br>';

  14. asyn_sendmail();

  15. echo time().'<br>';

  16. ?>

sendmail.php,執行耗時 10 秒

複制

  1. <?php
  2. //sendmail();
  3. //sleep 10 seconds
  4. sleep(10);
  5. fopen('C:\'.time(),'w');
  6. ?>

通過頁面通路 write.php,頁面輸出:

1272472697 call asyn_sendmail

1272472698

并且在 C:\ 生成檔案:

1272472708

從上面的結果可知 sendmail.php 花費至少 10 秒,但不會阻塞到 write.php 的繼續往下執行,表明這一過程是異步的。