PHP編程中的併發

PHP編程中的併發

週末去北京面了兩個公司,認識了幾位技術牛人,面試中聊了不少,感受收穫頗豐。認識到了本身的不足之處,也堅決了本身對計算機學習的信心。本文是對其中一道面試題的總結。php

面試中有一個問題沒有很好的回答出來,題目爲:併發3個http請求,只要其中一個請求有結果,就返回,並中斷其餘兩個。golang

當時考慮的內容有些偏離題目原意, 一直在考慮如何中斷http請求,大概是在 client->recv() 以前去判斷結果是否已經產生,因此回答的是用 socket 去發送一個 http 請求,把 socket 加入 libevent 循環監聽,在callback中判斷是否已經獲得結果,若是已經獲得結果,就直接 return。面試

後來本身越說越以爲不對,既然已經recv到結果,就不能算是中斷http請求。況且本身歷來沒用過libevent。後來講了還說了兩種實現,一個是用 curl_multi_init, 另外一個是用golang實現併發。
golang的版本當時忘了close的用法,結果並不太符合題意。編程

這題沒答上來,考官也沒爲難我。可是內心一直在考慮,直到面試完走到樓下有點明白什麼意思了,可能考的是併發,進程線程的應用。因此總結了這篇文章,來說講PHP中的併發。
本文大約總結了PHP編程中的五種併發方式,最後的Golang的實現純屬無聊,能夠無視。若是有空,會再補充一個libevent的版本。segmentfault

curl_multi_init

文檔中說的是 Allows the processing of multiple cURL handles asynchronously. 確實是異步。這裏須要理解的是select這個方法,文檔中是這麼解釋的Blocks until there is activity on any of the curl_multi connections.。瞭解一下常見的異步模型就應該能理解,select, epoll,都頗有名,這裏引用一篇很是好的文章,有興趣看下解釋吧。swoole

<?php
// build the individual requests as above, but do not execute them
$ch_1 = curl_init('http://www.baidu.com/');
$ch_2 = curl_init('http://www.baidu.com/');
curl_setopt($ch_1, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch_2, CURLOPT_RETURNTRANSFER, true);

// build the multi-curl handle, adding both $ch
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch_1);
curl_multi_add_handle($mh, $ch_2);

// execute all queries simultaneously, and continue when all are complete
$running = null;
do {
   curl_multi_exec($mh, $running);
   $ch = curl_multi_select($mh);
   if($ch !== 0){
       $info = curl_multi_info_read($mh);
       if($info){
           var_dump($info);
           $response_1 = curl_multi_getcontent($info['handle']);
           echo "$response_1 \n";
           break;
       }
   }
} while ($running > 0);

//close the handles
curl_multi_remove_handle($mh, $ch_1);
curl_multi_remove_handle($mh, $ch_2);
curl_multi_close($mh);

這裏我設置的是,select獲得結果,就退出循環,而且刪除 curl resource, 從而達到取消http請求的目的。網絡

swoole_client

swoole_client提供了異步模式,我居然把這個忘了。這裏的sleep方法須要swoole版本大於等於1.7.21, 我還沒升到這個版本,因此直接exit也能夠。併發

<?php
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
//設置事件回調函數
$client->on("connect", function($cli) {
    $req = "GET / HTTP/1.1\r\n
    Host: www.baidu.com\r\n
    Connection: keep-alive\r\n
    Cache-Control: no-cache\r\n
    Pragma: no-cache\r\n\r\n";

    for ($i=0; $i < 3; $i++) {
        $cli->send($req);
    }
});
$client->on("receive", function($cli, $data){
    echo "Received: ".$data."\n";
    exit(0);
    $cli->sleep(); // swoole >= 1.7.21
});
$client->on("error", function($cli){
    echo "Connect failed\n";
});
$client->on("close", function($cli){
    echo "Connection close\n";
});
//發起網絡鏈接
$client->connect('183.207.95.145', 80, 1);

process

哎,居然忘了 swoole_process, 這裏就不用 pcntl 模塊了。可是寫完發現,這其實也不算是中斷請求,而是哪一個先到讀哪一個,忽視後面的返回值。curl

<?php

$workers = [];
$worker_num = 3;//建立的進程數
$finished = false;
$lock = new swoole_lock(SWOOLE_MUTEX);

for($i=0;$i<$worker_num ; $i++){
    $process = new swoole_process('process');
    //$process->useQueue();
    $pid = $process->start();
    $workers[$pid] = $process;
}

foreach($workers as $pid => $process){
    //子進程也會包含此事件
    swoole_event_add($process->pipe, function ($pipe) use($process, $lock, &$finished) {
        $lock->lock();
        if(!$finished){
            $finished = true;
            $data = $process->read();
            echo "RECV: " . $data.PHP_EOL;
        }
        $lock->unlock();
    });
}

function process(swoole_process $process){
    $response = 'http response';
    $process->write($response);
    echo $process->pid,"\t",$process->callback .PHP_EOL;
}

for($i = 0; $i < $worker_num; $i++) {
    $ret = swoole_process::wait();
    $pid = $ret['pid'];
    echo "Worker Exit, PID=".$pid.PHP_EOL;
}

pthreads

編譯pthreads模塊時,提示php編譯時必須打開ZTS, 因此貌似必須 thread safe 版本才能使用. wamp中多php正好是TS的,直接下了個dll, 文檔中的說明覆制到對應目錄,就在win下測試了。 還沒徹底理解,查到文章說 php 的 pthreads 和 POSIX pthreads是徹底不同的。代碼有些爛,還須要多看看文檔,體會一下。異步

<?php
class Foo extends Stackable {
    public $url;
    public $response = null;
    public function __construct(){
        $this->url = 'http://www.baidu.com';
    }
    public function run(){}
}

class Process extends Worker {
    private $text = "";
    public function __construct($text,$object){
        $this->text = $text;
        $this->object = $object;
    }
    public function run(){
        while (is_null($this->object->response)){
            print " Thread {$this->text} is running\n";
            $this->object->response = 'http response';
            sleep(1);
        }
    }
}

$foo = new Foo();

$a = new Process("A",$foo);
$a->start();

$b = new Process("B",$foo);
$b->start();

echo $foo->response;

yield

yield生成的generator,能夠中斷函數,並用send向 generator 發送消息。
稍後補充協程的版本。還在學習中。

Golang

用Go實現比較簡單, 回家後查了查 close,處理一下 panic就ok了。代碼以下:

package main

import (
    "fmt"
)

func main() {
    var result chan string = make(chan string, 1)
    for index := 0;  index< 3; index++ {
        go doRequest(result)
    }

    res, ok := <-result
    if ok {
        fmt.Println("received ", res)
    }

}

func doRequest(result chan string)  {
    response := "http response"
    defer func() {
        if x := recover(); x != nil {
            fmt.Println("Unable to send: %v", x)
        }
    }()
    result <- response
    close(result)
}

上面的幾個方法,除了 curl_multi_* 貌似符合題意外(不肯定,要看下源碼),其餘的方法都沒有中斷請求後recv()的操做, 若是獲得response後還有後續操做,那麼是有用的,不然並無什麼意義。想一想多是PHP操做粒度太大, 猜想用 C/C++ 應該能解決問題。

寫的時候沒有注意到一個問題,有些方式是返回值,有些直接打印了,這樣很差,應該統一使用返回值獲得請求結果。能力有限,先這樣吧。

最後要作個廣告,計蒜客是一家致力於計算機科學高端教育的公司,若是你對編程或者計算機底層有興趣,不妨去他們網站學習學習。
同時,公司也一直在招人,若是你對本身的能力有信心,能夠去試試。公司很是自由開放,90後爲主。牛人也有很多,ACM世界冠軍,知乎大牛。
公司主作教育,內部學習資料必須給力,我只看到了一些關於操做系統的測試題,涉及到的知識面很廣,可見公司平均技術能力有多厲害。

若是文章中有疏漏,錯誤,還請大神們不吝指出,幫助菜鳥進步,謝謝。

相關文章
相關標籤/搜索