首先 swoole 是 php 的一個(gè)擴(kuò)展程序
swoole 是一個(gè)為 php 用 c 和 c++ 編寫的基于事件的高性能異步 & 協(xié)程并行網(wǎng)絡(luò)通信引擎
swoole 是一個(gè)多進(jìn)程模型的框架,當(dāng)啟動(dòng)一個(gè)進(jìn)程 swoole 應(yīng)用時(shí),一共會(huì)創(chuàng)建 2+n+m 個(gè)進(jìn)程,n 為 worker 進(jìn)程數(shù),m 為 TaskWorker 進(jìn)程數(shù),1 個(gè) master 進(jìn)程和一個(gè) manager 進(jìn)程,關(guān)系如下圖所示
Master 進(jìn)程為主進(jìn)程,該進(jìn)程會(huì)創(chuàng)建 Manager 進(jìn)程、Reactor 線程等工作進(jìn) / 線程
1、Reactor 線程:
- 負(fù)責(zé)維護(hù)客戶端
TCP
連接、處理網(wǎng)絡(luò)IO
、處理協(xié)議、收發(fā)數(shù)據(jù) - 完全是異步非阻塞的模式
- 全部為
C
代碼,除Start
/Shudown
事件回調(diào)外,不執(zhí)行任何 PHP 代碼 - 將
TCP
客戶端發(fā)來的數(shù)據(jù)緩沖、拼接、拆分成完整的一個(gè)請(qǐng)求數(shù)據(jù)包 Reactor
以多線程的方式運(yùn)行
2、Worker 進(jìn)程:
- 接受由
Reactor
線程投遞的請(qǐng)求數(shù)據(jù)包,并執(zhí)行PHP
回調(diào)函數(shù)處理數(shù)據(jù) - 生成響應(yīng)數(shù)據(jù)并發(fā)給
Reactor
線程,由Reactor
線程發(fā)送給TCP
客戶端 - 可以是異步非阻塞模式,也可以是同步阻塞模式
Worker
以多進(jìn)程的方式運(yùn)行
3、TaskWorker 進(jìn)程 :
- 接受由
Worker
進(jìn)程通過swoole_server->task/taskwait
方法投遞的任務(wù) - 處理任務(wù),并將結(jié)果數(shù)據(jù)返回(使用
swoole_server->finish
)給Worker
進(jìn)程 - 完全是同步阻塞模式
TaskWorker
以多進(jìn)程的方式運(yùn)行
A: 網(wǎng)絡(luò)通信引擎
網(wǎng)絡(luò)通信引擎,是為 php 提供網(wǎng)絡(luò)通信能力的,傳統(tǒng)的 php 程序都是啟動(dòng) php-fpm,前邊再有一層 nginx 或者 apache,打開瀏覽器訪問就 OK,但是從瀏覽器訪問到服務(wù)器這一階段涉及到了網(wǎng)絡(luò)強(qiáng)求,但是這一階段跟 php 腳本沒有任何的關(guān)系,php 只需要處理好數(shù)據(jù)生成需要展示的內(nèi)容就完成使命了,聲明周期當(dāng)中,請(qǐng)求到來前和請(qǐng)求完成后都沒有 php 腳本什么事,而 swoole 提供的一大能力就是擴(kuò)展了 php 的生命周期,無需 php-fpm 或者 nginx 或者 apache 之類的工具幫助就可以啟動(dòng)一個(gè) web 服務(wù),并且從服務(wù)啟動(dòng)前,啟動(dòng)后,鏈接進(jìn)入,請(qǐng)求到來,請(qǐng)求結(jié)束,鏈接切斷,服務(wù)終止都在 php 腳本的掌控之中,這樣的話 php 腳本就會(huì)涉及到大量的網(wǎng)絡(luò)通訊處理,而這個(gè)網(wǎng)絡(luò)通訊處理的能力正是來源于 swoole! 網(wǎng)絡(luò)通信引擎,是為 php 提供網(wǎng)絡(luò)通信能力的,傳統(tǒng)的 php 程序都是啟動(dòng) php-fpm,前邊再有一層 nginx 或者 apache,打開瀏覽器訪問就 OK,但是從瀏覽器訪問到服務(wù)器這一階段涉及到了網(wǎng)絡(luò)強(qiáng)求,但是這一階段跟 php 腳本沒有任何的關(guān)系,php 只需要處理好數(shù)據(jù)生成需要展示的內(nèi)容就完成使命了,聲明周期當(dāng)中,請(qǐng)求到來前和請(qǐng)求完成后都沒有 php 腳本什么事,而 swoole 提供的一大能力就是擴(kuò)展了 php 的生命周期,無需 php-fpm 或者 nginx 或者 apache 之類的工具幫助就可以啟動(dòng)一個(gè) web 服務(wù),并且從服務(wù)啟動(dòng)前,啟動(dòng)后,鏈接進(jìn)入,請(qǐng)求到來,請(qǐng)求結(jié)束,鏈接切斷,服務(wù)終止都在 php 腳本的掌控之中,這樣的話 php 腳本就會(huì)涉及到大量的網(wǎng)絡(luò)通訊處理,而這個(gè)網(wǎng)絡(luò)通訊處理的能力正是來源于 swoole!
B:基于事件的高性能異步
同步:
就拿讀取文件內(nèi)容來說吧
file_get_contents () 執(zhí)行完才能執(zhí)行下邊的代碼 這樣就很容易造成程序的阻塞
否則下邊的代碼就無法輸出文件的內(nèi)容
傳統(tǒng) php 都是這樣阻塞式的順序執(zhí)行的
這是常見的同步編程
異步:
代碼在執(zhí)行到 ajax 的時(shí)候,函數(shù)會(huì)直接返回,你馬上就可以看到屏幕上打印出的 lol
這就是異步,這樣你永遠(yuǎn)不會(huì)被 IO 阻塞,但是它帶來了新的問題,在你運(yùn)行到 lol 之后你就不知道現(xiàn)在代碼運(yùn)行到哪里去了,你只能等待回調(diào)被觸發(fā),然后屏幕上打印響應(yīng)的 log, 它的執(zhí)行不是單層順序的,而是嵌套的
如果在業(yè)務(wù)代碼當(dāng)中 這樣層層嵌套可讀性可想而知
當(dāng)然這是前端異步請(qǐng)求后端接口
swoole 當(dāng)中處理異步回調(diào)嵌套使用的是協(xié)程
你知道什么叫協(xié)程嗎?你知道線程是干啥的嗎?你又知道進(jìn)程嗎?
如果想深入了解 swoole 的強(qiáng)大之處 你還得要了解傳統(tǒng) php 的 lnmp 環(huán)境的整套運(yùn)行機(jī)制,這些你都了解嗎?
目前 swoole 當(dāng)中的異步也是協(xié)程化的,所以你必須充分理解協(xié)程到底是個(gè)什么東西!
C:協(xié)程
到底什么是協(xié)程?
通俗的說,協(xié)程就是一段段協(xié)作方式執(zhí)行的程序,協(xié)作來完成一件事,協(xié)作就是協(xié)同作業(yè)。我們知道團(tuán)隊(duì)協(xié)同來做事,比個(gè)人單獨(dú)來做事,效率肯定要高,因?yàn)閳F(tuán)隊(duì)協(xié)同可以發(fā)揮各成員的能動(dòng)性、優(yōu)勢互補(bǔ)。這是拿人來比喻。我們拿做事來比喻舉個(gè)例子:比如我們做飯,比如有以下環(huán)節(jié)洗菜、切菜、燒水、炒菜、煮米飯,人作為主體來操作,那么如果按部就班的做,先燒水,再洗菜,在切菜,再炒菜,再煮飯,那這頓飯要做很長時(shí)間比如總共 30 分鐘吧,如果我們通過協(xié)同方式,先燒水,放灶火上就可以做其他洗菜、切菜的準(zhǔn)備,再煮米飯,然后再來洗菜、切菜,再查看煮米飯,再炒菜,…,如此循環(huán)往復(fù)切換,最后水燒好,米飯也煮好了,菜也炒好了,飯也 OK 了,這樣我們耗時(shí)可能只有 10-15 分鐘,看到了嗎,這就是生活中的 “協(xié)程”,由人來合理調(diào)度安排不同的環(huán)節(jié),充分利用各種不同的資源和時(shí)間,來達(dá)到提高效率。協(xié)程是計(jì)算機(jī)程序,調(diào)用的則是不同的程序,處理者主要由 CPU 完成,處理對(duì)象是各種 IO 資源,處理的方式是不同的語言編寫的程序。我們知道,CPU 可以調(diào)度不同的程序,讓程序調(diào)用不同的 IO 資源,最初的進(jìn)程是通過 CPU 頻繁的切換來完成調(diào)用程序的,是操作系統(tǒng)按一定算法分配的時(shí)間片搶占被動(dòng)方式來切換的,未考慮程序?qū)嶋H執(zhí)行狀況,這樣切換程序會(huì)帶來一定問題,而協(xié)程作為一種新的工作模式,可以讓程序協(xié)作方式來執(zhí)行,在需要使用 CPU 時(shí),交給程序處理,遇到耗時(shí)的 IO 資源操作時(shí)會(huì)讓出 CPU,交給處理其他程序,這樣互相協(xié)作來執(zhí)行,而不是搶占式的,就像交通規(guī)則,大家都遵守按一定規(guī)則禮讓先行,不隨便搶道,協(xié)同方式,程序都會(huì)執(zhí)行的良好。
我們來看具體的案例:
go(function () {
echo "hello go1 \n";
});
echo "hello main \n";
go(function () {
echo "hello go2 \n";
});
上面的代碼執(zhí)行結(jié)果:
root@b98940b00a9b /v/w/c/p/swoole# php co.php
hello go1
hello main
hello go2
執(zhí)行結(jié)果和我們平時(shí)寫代碼的順序,好像沒啥區(qū)別。實(shí)際執(zhí)行過程:
- 運(yùn)行此段代碼,系統(tǒng)啟動(dòng)一個(gè)新進(jìn)程
- 遇到
go()
, 當(dāng)前進(jìn)程中生成一個(gè)協(xié)程,協(xié)程中輸出heelo go1
, 協(xié)程退出 - 進(jìn)程繼續(xù)向下執(zhí)行代碼,輸出
hello main
- 再生成一個(gè)協(xié)程,協(xié)程中輸出
heelo go2
, 協(xié)程退出
我們來稍微改一改,體驗(yàn)協(xié)程的調(diào)度:
use Co;
go(function () {
Co::sleep(1); // 只新增了一行代碼
echo "hello go1 \n";
});
echo "hello main \n";
go(function () {
echo "hello go2 \n";
});
\Co::sleep () 函數(shù)功能和 sleep () 差不多,但是它模擬的是 IO 等待 (IO 后面會(huì)細(xì)講). 執(zhí)行的結(jié)果如下:
root@b98940b00a9b /v/w/c/p/swoole# php co.php
hello main
hello go2
hello go1
怎么不是順序執(zhí)行的呢?實(shí)際執(zhí)行過程:
- 運(yùn)行此段代碼,系統(tǒng)啟動(dòng)一個(gè)新進(jìn)程
- 遇到
go()
, 當(dāng)前進(jìn)程中生成一個(gè)協(xié)程 - 協(xié)程中遇到 IO 阻塞 (這里是
Co::sleep()
模擬出的 IO 等待), 協(xié)程讓出控制,進(jìn)入?yún)f(xié)程調(diào)度隊(duì)列 - 進(jìn)程繼續(xù)向下執(zhí)行,輸出
hello main
- 執(zhí)行下一個(gè)協(xié)程,輸出
hello go2
- 之前的協(xié)程準(zhǔn)備就緒,繼續(xù)執(zhí)行,輸出
hello go1
到這里,已經(jīng)可以看到 swoole 中 協(xié)程與進(jìn)程的關(guān)系 , 以及 協(xié)程的調(diào)度 , 我們再改一改剛才的程序
go(function () {
Co::sleep(1);
echo "hello go1 \n";
});
echo "hello main \n";
go(function () {
Co::sleep(1);
echo "hello go2 \n";
});
我想你已經(jīng)知道輸出是什么樣子了:
root@b98940b00a9b /v/w/c/p/swoole# php co.php
hello main
hello go1
hello go2
協(xié)程快在哪?減少 IO 阻塞導(dǎo)致的性能損失
大家可能聽到使用協(xié)程的最多的理由,可能就是 協(xié)程快. 那看起來和平時(shí)寫得差不多的代碼,為什么就要快一些呢?一個(gè)常見的理由是,可以創(chuàng)建很多個(gè)協(xié)程來執(zhí)行任務(wù),所以快. 這種說法是對(duì)的,不過還停留在表面
首先,一般的計(jì)算機(jī)任務(wù)分為 2 種
- CPU 密集型,比如加減乘除等科學(xué)計(jì)算
- IO 密集型,比如網(wǎng)絡(luò)請(qǐng)求,文件讀寫等
其次,高性能相關(guān)的 2 個(gè)概念 - 并行:同一個(gè)時(shí)刻,同一個(gè) CPU 只能執(zhí)行同一個(gè)任務(wù),要同時(shí)執(zhí)行多個(gè)任務(wù),就需要有多個(gè) CPU 才行
- 并發(fā):由于 CPU 切換任務(wù)非??欤斓饺祟惪梢愿兄臉O限,就會(huì)有很多任務(wù) 同時(shí)執(zhí)行 的錯(cuò)覺
了解了這些,我們再來看協(xié)程,協(xié)程適合的是 IO 密集型 應(yīng)用,因?yàn)閰f(xié)程在 IO 阻塞 時(shí)會(huì)自動(dòng)調(diào)度,減少 IO 阻塞導(dǎo)致的時(shí)間損失!
協(xié)程在遇到 IO 阻塞的時(shí)候會(huì)讓出 cpu 的控制權(quán),其他協(xié)程拿到去執(zhí)行其他協(xié)程的任務(wù),當(dāng) IO 阻塞過去之后回過頭來繼續(xù)往下執(zhí)行!
要點(diǎn):
- 協(xié)程在阻塞的時(shí)候只是阻塞了當(dāng)前這個(gè)協(xié)程 并不會(huì)阻塞整個(gè)的進(jìn)程 因?yàn)閰f(xié)程是在線程內(nèi)部的,即使阻塞了也會(huì)讓出控制權(quán),掛起,等待當(dāng)前協(xié)程的 IO 不阻塞在回過頭來繼續(xù)執(zhí)行,也就是同步的代碼完成了異步的功能!相當(dāng)強(qiáng)悍!
- 從宏觀的角度看,程序員搞出來的多個(gè)協(xié)程在不發(fā)生任何協(xié)程阻塞的前提是是順序執(zhí)行的 一旦發(fā)生阻塞 你可以把多個(gè)協(xié)程理解為并行的 同時(shí)在執(zhí)行的!
- 協(xié)程是在單進(jìn)程單線程當(dāng)中實(shí)現(xiàn)的 你可以在里面實(shí)現(xiàn)成千上萬的協(xié)程 并且效果極高! 每個(gè)協(xié)程去干不同的事!協(xié)作制的無需加鎖沒有搶占,串行的!什么叫串行呢?每次執(zhí)行一個(gè)協(xié)程 遇到 IO 阻塞 掛起 執(zhí)行接下來的程序 可能還是個(gè)協(xié)程 如果再遇到 IO 阻塞再掛起 繼續(xù)往下執(zhí)行 當(dāng) IO 阻塞完成回過頭來繼續(xù)往下執(zhí)行沒執(zhí)行完的協(xié)程程序 每次都是一個(gè)協(xié)程在執(zhí)行,串行化的!
- 協(xié)程之間每秒可以進(jìn)行百萬千萬次切換! 線程之間切換需要加鎖 加鎖就很浪費(fèi)資源!進(jìn)程間切換更浪費(fèi)資源,因?yàn)樯暇€文很大!更多詳細(xì)內(nèi)容請(qǐng)自行百度!
- 協(xié)程很小切換還快 每秒百萬千萬級(jí)別的切換 所以 一個(gè)進(jìn)程里面 只要你的內(nèi)存夠用 你就可以無止境的創(chuàng)造協(xié)程出來干事情!
- 事件驅(qū)動(dòng)和異步為 swoole 提供了高性能 而協(xié)程解決了異步回調(diào)代碼嵌套的問題 提高了代碼可讀性和維護(hù)性 也是 swoole 最大的特色!
d:混合服務(wù)器
你可以隨意創(chuàng)建一個(gè) http tcp websocket http2 服務(wù) 并且能輕松承載成千上萬的請(qǐng)求
這個(gè)可以去官網(wǎng) 看如何創(chuàng)建 http 服務(wù) tcp 服務(wù) websocket 服務(wù)
websocket 是 http 升級(jí)而來的 創(chuàng)建了 websocket 服務(wù)就自帶了 http 服務(wù)
創(chuàng)建好服務(wù) 直接 start 就可以運(yùn)行一個(gè) http 服務(wù)或者其他 tcp 等的服務(wù)了 而無需 nginx apache 的任何參與!
有服務(wù)端必有客戶端 我們可以在代碼里面通過
new Swoole\Coroutine\Http\Client(127.0.0.1,9501);
去鏈接 http 服務(wù)器
當(dāng)然其他的也一樣 自己去看手冊 這里只是說明 swoole 有多么的強(qiáng)大
e:協(xié)程之間通訊 channel
通道(channel)是協(xié)程之間通信交換數(shù)據(jù)的唯一渠道,而協(xié)程 + 通道的開發(fā)組合即為著名的 csp 編程模型
在 swoole 當(dāng)中 channel 常用于連接池的實(shí)現(xiàn)和協(xié)程并發(fā)的調(diào)度
如圖所示 第一個(gè)協(xié)程執(zhí)行完成之后我們會(huì)往 channel 通道當(dāng)中 push 一個(gè)元素 第二個(gè)也是 當(dāng)然第一個(gè)一定是 IO 阻塞的 第二個(gè)沒有 我們在 for 循環(huán)里面 獲取 channel 里面的值的時(shí)候由于第一個(gè)阻塞 $chan->pop () 也是阻塞的 因?yàn)檎w都是在一個(gè)協(xié)程里面 只有當(dāng)大的協(xié)程里面的兩個(gè)小協(xié)程都完成了 這里的 $chan->pop 才會(huì)執(zhí)行 接觸阻塞 最后才會(huì)執(zhí)行 echo 語句! 這是 swoole 當(dāng)中協(xié)程并發(fā)的一個(gè)很好的案例應(yīng)用
f:毫秒定時(shí)器
毫秒定時(shí)器是異步回調(diào)的方式來實(shí)現(xiàn)的!
還可以使用協(xié)程方式 采用同步阻塞的方式來實(shí)現(xiàn)定時(shí)器!
之前用 sleep 會(huì)阻塞整個(gè)進(jìn)程 現(xiàn)在你在協(xié)程里面搞 Co:sleep (0.1) 阻塞的是當(dāng)前協(xié)程 而不會(huì)阻塞整個(gè)進(jìn)程!