首頁  >  文章  >  後端開發  >  php為什麼需要非同步程式設計? php非同步程式設計的詳解(附範例)

php為什麼需要非同步程式設計? php非同步程式設計的詳解(附範例)

不言
不言轉載
2019-01-23 10:11:043229瀏覽

這篇文章帶給大家的內容是關於php為什麼需要非同步程式設計? php非同步程式設計的詳解(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

我對 php 非同步的知識還比較混亂,寫這篇是為了整理,可能有錯。

傳統的 php-fpm 一個行程執行一個請求,要達到多少並發,就要產生多少個行程。更糟的是每次請求都需要重新編譯執行,導致並發一直上不來。因此出現了 Swoole 和 WorkerMan 兩個國內流行的常駐記憶體框架[1]。這兩個框架原理都是透過事件循環,讓程式一直停留在內存,等待外部請求,達到高並發。

為什麼需要非同步

先來看一個範例

#在工作目錄下新建檔案slowServer.php

<?php
sleep(5); // 5秒后才能返回请求
echo &#39;done&#39;;

開啟服務

$ php -S localhost:8081 slowServer.php

開另一個終端,安裝依賴

$ pecl install event # 安装 event 扩展
$ composer require workerman/workerman
$ composer require react/http-client:^0.5.9

新檔案worker.php

require_once __DIR__ . &#39;/vendor/autoload.php&#39;;
use Workerman\Worker;
use Workerman\Connection\AsyncTcpConnection;
use Amp\Artax\Response;

$http_worker = new Worker("http://0.0.0.0:8082");

$http_worker->count = 1; // 只开一个进程

$http_worker->onMessage = function($connection, $host) {
    echo 1;
    $data = file_get_contents('http://localhost:8081');
    $connection->send($data);
};

Worker::runAll();

開啟伺服器

php worker.php start

在瀏覽器開啟兩個標籤,都開啟網址http://localhost:8082 。這時可以看到終端輸出“1”,過了一會兒又輸出“1”,原因是8081伺服器在處理第一個請求的時候阻塞在了等待8081返回之中,等第一個請求結束後,才開始處理第二個請求。也就是說請求是一個一個執行的,要達到多少個並發,就要建立多少個進程,跟 php-fpm 一樣。現在修改一下程式碼

$http_worker->onMessage = function($connection, $host) {
    echo 1;
    $loop    = Worker::getEventLoop();
    $client  = new \React\HttpClient\Client($loop);
    $request = $client->request('GET', 'http://localhost:8081');
    $request->on('error', function(Exception $e) use ($connection) {
        $connection->send($e);
    });
    $request->on('response', function ($response) use ($connection) {
        $response->on('data', function ($data) use ($connection) {
            $connection->send($data);
        });
    });
    $request->end();
};

現在開啟服務,再在瀏覽器發起請求,發現第二個「1」在請求後就馬上輸出了,而這時第一個請求還沒結束。這表示進程不再阻塞,並發量取決於 cpu 和 內存,而不是進程數。

為什麼需要非同步

透過上面的範例已經很明白了,reactphp 框架透過把http 請求變成非同步,讓onMessage 函數變成非阻塞,cpu 可以去處理下一個請求。即從 cpu 循環等待 8081 返回,變成了 epoll 等待。

非同步的意義在於把 cpu 從 io 等待中解放出來,可以處理其他運算任務。 如果你想知道怎麼用框架實現非同步,看到這裡就可以了。 WorkerMan 配合 ReactPHP 或自身的 AsyncTcpConnection 已經可以滿足許多 io 要求非同步化的需求。以下繼續討論這些框架是怎麼做到異步的。

哪些地方應該被做成非同步

透過上面的例子已經知道一旦執行到不需要cpu,但是要等待io 的時候,應該把io 的過程做成異步。

實作事件循環

上面的範例是透過reactphp 把http 請求變成了非同步,其實WorkerMan 框架本身也是異步的,下面來看看WorkerMan 是怎麼使onMessage 函數可以非同步接受請求。先來新建下面這個檔案react.php

<?php
$context = stream_context_create();
$socket = stream_socket_server(&#39;tcp://0.0.0.0:8081&#39;, $errno, $errmsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,$context); // 注册一个 fd(file descriptor)

function react($socket){
    $new_socket = stream_socket_accept($socket, 0, $remote_address);
    echo 1;
}

$eventBase = new EventBase();
$event = new Event($eventBase, $socket, Event::READ | Event::PERSIST, &#39;react&#39;, $socket); // 注册一个事件,检测 fd 有没有写入内容
$event->add();
$eventBase->loop(); // 开始循环

開始執行

$ php react.php

在另一個終端機執行

telnet 127.0.0.1 8081

這時就會看到第一個終端輸出'1 '。

我之前寫過一篇文章《php使用epoll》,是這篇文章的基礎。那篇文章裡事件回調是透過定時來實現,即

$event->add($seconds);

而這裡,事件回調是透過偵測 fd 是否有寫入內容來實現,這個過程不需要 cpu 參與。當 fd 有內容寫入時,會調函數 'react',這時開始使用 cpu。如果這時候進程執行另一個非同步請求,例如用 reactphp 框架請求一個網頁,那麼程式會讓出 cpu,此時如果有另一個請求進來,就可以回調執行另一個 'react' 函數。由此提高了並發量。

協程

生成器Generater

這是生成器的PHP 官方文件http:// php.net/manual/zh/lang...

<?php
function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        //注意变量$i的值在不同的yield之间是保持传递的。
        yield $i;
    }
}

$generator = gen_one_to_three();
foreach ($generator as $value) {
    echo "$value\n";
}

產生器就是每次程式執行到yield 的時候保存狀態,然後返回$i,是否繼續執行gen_one_to_three 裡的循環,取決於主程式是否繼續呼叫

什麼是協程

上面的程式另一種寫法是

<?php
$i = 1;
function gen_one_to_three() {
    global $i;
    if ($i<=3){
        return $i++;
    }
}

while ($value = gen_one_to_three()) {
    echo "$value\n";
}

由此可見,協程就是一種對函數的封裝,使其變成一種可以被中斷的函數,行為更像是子程序或子線程,而不是函數。協程的具體寫法這裡不細寫,因為協程的寫法十分複雜,可能需要再做一層封裝才能好用。

協程與非同步

既然協程可以被中斷,那麼只要在程式發起請求後發起事件循環,然後用yield 返回,然後程式繼續執行主程式部分,等事件返回後觸發函數,執行Generatot::next() 或Generator::send() 繼續執行協程部分。封裝好後好像沒有非同步回呼函數一樣,跟同步函數很像。

現在已經有 ampphp 和 swoole 兩個框架封裝了協程,有興趣可以了解一下。

以上是php為什麼需要非同步程式設計? php非同步程式設計的詳解(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除