摘要:配合或者自身的已經(jīng)可以滿足很多請(qǐng)求異步化的需求。協(xié)程與異步既然協(xié)程可以被中斷,那么只要在程序發(fā)起請(qǐng)求后發(fā)起事件循環(huán),然后用返回,然后程序繼續(xù)執(zhí)行主程序部分,等事件返回后觸發(fā)函數(shù),執(zhí)行或來(lái)繼續(xù)執(zhí)行協(xié)程部分。
前言
我對(duì) php 異步的知識(shí)還比較混亂,寫這篇是為了整理,可能有錯(cuò)。
傳統(tǒng)的 php-fpm 一個(gè)進(jìn)程執(zhí)行一個(gè)請(qǐng)求,要達(dá)到多少并發(fā),就要生成多少個(gè)進(jìn)程。更糟糕的是每次請(qǐng)求都需要重新編譯執(zhí)行,導(dǎo)致并發(fā)一直上不來(lái)。因此出現(xiàn)了 Swoole 和 WorkerMan 兩個(gè)國(guó)內(nèi)流行的常駐內(nèi)存框架[1]。這兩個(gè)框架原理都是通過(guò)事件循環(huán),讓程序一直停留在內(nèi)存,等待外部請(qǐng)求,達(dá)到高并發(fā)。
為什么需要異步 先來(lái)看一個(gè)例子在工作目錄下新建文件 slowServer.php
開啟服務(wù)
$ php -S localhost:8081 slowServer.php開另一個(gè)終端,安裝依賴
$ pecl install event # 安裝 event 擴(kuò)展 $ composer require workerman/workerman $ composer require react/http-client:^0.5.9新建文件 worker.php
require_once __DIR__ . "/vendor/autoload.php"; use WorkermanWorker; use WorkermanConnectionAsyncTcpConnection; use AmpArtaxResponse; $http_worker = new Worker("http://0.0.0.0:8082"); $http_worker->count = 1; // 只開一個(gè)進(jìn)程 $http_worker->onMessage = function($connection, $host) { echo 1; $data = file_get_contents("http://localhost:8081"); $connection->send($data); }; Worker::runAll();開啟服務(wù)器
php worker.php start在瀏覽器開啟兩個(gè)標(biāo)簽,都打開網(wǎng)址 http://localhost:8082 。這時(shí)可以看到終端輸出“1”,過(guò)了一會(huì)兒又輸出“1”,原因是8081服務(wù)器在處理第一個(gè)請(qǐng)求的時(shí)候阻塞在了等待8081返回之中,等第一個(gè)請(qǐng)求結(jié)束后,才開始處理第二個(gè)請(qǐng)求。也就是說(shuō)請(qǐng)求是一個(gè)一個(gè)執(zhí)行的,要達(dá)到多少個(gè)并發(fā),就要建立多少個(gè)進(jìn)程,跟 php-fpm 一樣?,F(xiàn)在修改一下代碼
$http_worker->onMessage = function($connection, $host) { echo 1; $loop = Worker::getEventLoop(); $client = new ReactHttpClientClient($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(); };現(xiàn)在打開服務(wù),再在瀏覽器發(fā)起請(qǐng)求,發(fā)現(xiàn)第二個(gè)“1”在請(qǐng)求后就馬上輸出了,而這時(shí)第一個(gè)請(qǐng)求還沒結(jié)束。這表明進(jìn)程不再阻塞,并發(fā)量取決于 cpu 和 內(nèi)存,而不是進(jìn)程數(shù)。
為什么需要異步通過(guò)上面的例子已經(jīng)很明白了,reactphp 框架通過(guò)把 http 請(qǐng)求變成異步,讓 onMessage 函數(shù)變成非阻塞,cpu 可以去處理下一個(gè)請(qǐng)求。即從 cpu 循環(huán)等待 8081 返回,變成了 epoll 等待。
異步的意義在于把 cpu 從 io 等待中解放出來(lái),可以處理其他計(jì)算任務(wù)。 如果你想知道怎么用框架實(shí)現(xiàn)異步,看到這里就可以了。WorkerMan 配合 ReactPHP 或者自身的 AsyncTcpConnection 已經(jīng)可以滿足很多 io 請(qǐng)求異步化的需求。下面繼續(xù)討論這些框架是怎么做到異步的。
哪些地方應(yīng)該被做成異步通過(guò)上面的例子已經(jīng)知道一旦執(zhí)行到不需要 cpu,但是要等待 io 的時(shí)候,應(yīng)該把 io 的過(guò)程做成異步。
實(shí)現(xiàn)事件循環(huán)上面的例子是通過(guò) reactphp 把 http 請(qǐng)求變成了異步,其實(shí) WorkerMan 框架本身也是異步的,下面來(lái)看看 WorkerMan 是怎么使 onMessage 函數(shù)可以異步接受請(qǐng)求。先來(lái)新建下面這個(gè)文件 react.php
add(); $eventBase->loop(); // 開始循環(huán)開始執(zhí)行
$ php react.php在另一個(gè)終端執(zhí)行
telnet 127.0.0.1 8081這時(shí)就會(huì)看到第一個(gè)終端輸出"1"。
我之前寫過(guò)一篇文章《php使用epoll》,是這篇文章的基礎(chǔ)。那篇文章里事件回調(diào)是通過(guò)定時(shí)來(lái)實(shí)現(xiàn),即
$event->add($seconds);而這里,事件回調(diào)是通過(guò)檢測(cè) fd 是否有寫入內(nèi)容來(lái)實(shí)現(xiàn),這個(gè)過(guò)程不需要 cpu 參與。當(dāng) fd 有內(nèi)容寫入時(shí),會(huì)調(diào)函數(shù) "react",這時(shí)開始使用 cpu。如果這時(shí)候進(jìn)程執(zhí)行另一個(gè)異步請(qǐng)求,比如用 reactphp 框架請(qǐng)求一個(gè)網(wǎng)頁(yè),那么程序會(huì)讓出 cpu,此時(shí)如果有另一個(gè)請(qǐng)求進(jìn)來(lái),就可以回調(diào)執(zhí)行另一個(gè) "react" 函數(shù)。由此提高了并發(fā)量。
協(xié)程 生成器 Generater這是生成器的 PHP 官方文檔 http://php.net/manual/zh/lang...
生成器就是每次程序執(zhí)行到 yield 的時(shí)候保存狀態(tài),然后返回 $i,是否繼續(xù)執(zhí)行 gen_one_to_three 里的循環(huán),取決于主程序是否繼續(xù)調(diào)用
什么是協(xié)程上面的程序另一種寫法是
由此可見,協(xié)程就是一種對(duì)函數(shù)的封裝,使其變成一種可以被中斷的函數(shù),行為更像是子進(jìn)程或子線程,而不是函數(shù)。協(xié)程的具體寫法這里不細(xì)寫,因?yàn)閰f(xié)程的寫法十分復(fù)雜,可能需要再做一層封裝才能好用。
協(xié)程與異步既然協(xié)程可以被中斷,那么只要在程序發(fā)起請(qǐng)求后發(fā)起事件循環(huán),然后用 yield 返回,然后程序繼續(xù)執(zhí)行主程序部分,等事件返回后觸發(fā)函數(shù),執(zhí)行 Generatot::next() 或 Generator::send() 來(lái)繼續(xù)執(zhí)行協(xié)程部分。封裝好后就好像沒有異步回調(diào)函數(shù)一樣,和同步函數(shù)很像。
現(xiàn)在已經(jīng)有 ampphp 和 swoole 兩個(gè)框架封裝了協(xié)程,有興趣可以了解一下。
國(guó)外還有 https://amphp.org 和 https://reactphp.org 這兩個(gè)框架
博客地址:http://b.ljj.pub
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/30010.html
摘要:下文如無(wú)特殊聲明將使用進(jìn)程同時(shí)表示進(jìn)程線程。收到數(shù)據(jù)后服務(wù)器程序進(jìn)行處理然后使用向客戶端發(fā)送響應(yīng)?,F(xiàn)在各種高并發(fā)異步的服務(wù)器程序都是基于實(shí)現(xiàn)的,比如。 并發(fā) IO 問(wèn)題一直是服務(wù)器端編程中的技術(shù)難題,從最早的同步阻塞直接 Fork 進(jìn)程,到 Worker 進(jìn)程池/線程池,到現(xiàn)在的異步IO、協(xié)程。PHP 程序員因?yàn)橛袕?qiáng)大的 LAMP 框架,對(duì)這類底層方面的知識(shí)知之甚少,本文目的就是詳細(xì)介...
摘要:四異步編程解決方案模式模式一定程度上緩解了嵌套回調(diào)的問(wèn)題,只會(huì)處在未完成完成態(tài)失敗態(tài)中的一種,只會(huì)從未完成轉(zhuǎn)化為完成態(tài)或者失敗態(tài),不能逆轉(zhuǎn)。 一、從一個(gè)簡(jiǎn)單的案例開始 fs.readdir(path.join(__dirname, ./index.js), (err, files) => { files.foreach((filename, index) => { ...
摘要:實(shí)現(xiàn)異步的方式有哪些提供了一些異步方法那它們底層是用哪種方式實(shí)現(xiàn)的呢實(shí)現(xiàn)異步的方式有線程方式進(jìn)程方式復(fù)用線程方式和進(jìn)程方式類似有異步請(qǐng)求時(shí)開一個(gè)線程或者進(jìn)程獲取到數(shù)據(jù)后線程間可以直接共享數(shù)據(jù)進(jìn)程間可以通過(guò)進(jìn)程通信機(jī)制,如共享內(nèi)存管道等方式進(jìn) 實(shí)現(xiàn)異步的方式有哪些? swoole提供了一些異步方法, 那它們底層是用哪種方式實(shí)現(xiàn)的呢? 實(shí)現(xiàn)異步的方式有: 線程方式 進(jìn)程方式 IO復(fù)用 ...
摘要:異步編程基于實(shí)現(xiàn)框架說(shuō)明偶然間在上看到有贊官方倉(cāng)庫(kù)的手把手教你實(shí)現(xiàn)與。由于此前用過(guò),對(duì)于的洋蔥模型嘆為觀止。文檔中是基于擴(kuò)展進(jìn)行開發(fā),而對(duì)并不友好,向來(lái)習(xí)慣在下開發(fā)的我一鼓作氣,將改寫并兼容了此項(xiàng)目。 PHP異步編程: 基于 PHP 實(shí)(chao)現(xiàn)(xi) NODEJS web框架 KOA 說(shuō)明 偶然間在 GITHUB 上看到有贊官方倉(cāng)庫(kù)的 手把手教你實(shí)現(xiàn)co與Koa 。由于此前用過(guò)...
摘要:現(xiàn)在在后端業(yè)務(wù)開發(fā)編程方面,技術(shù)力量強(qiáng)的團(tuán)隊(duì)已經(jīng)開始將技術(shù)棧從同步模式切換為異步了。使用這些技術(shù)方案是無(wú)法兼容已有程序的。影響了異步回調(diào)技術(shù)棧的普及。將會(huì)成為未來(lái)后端開發(fā)領(lǐng)域的主流技術(shù)方案。 今天太忙,少寫一點(diǎn),后面再補(bǔ)充。 異步模式 Go 語(yǔ)言越來(lái)越熱門,很多大型互聯(lián)網(wǎng)公司后端正在轉(zhuǎn)向 GO 。Java 圈知名的服務(wù)化框架 Dubbo 也宣布轉(zhuǎn)型異步模式。這是一個(gè)大趨勢(shì),異步模式已經(jīng)...
閱讀 1170·2019-08-30 12:44
閱讀 676·2019-08-29 13:03
閱讀 2588·2019-08-28 18:15
閱讀 2451·2019-08-26 10:41
閱讀 3124·2019-08-26 10:28
閱讀 3063·2019-08-23 16:54
閱讀 2015·2019-08-23 15:16
閱讀 844·2019-08-23 14:55