date: 2019-04-30 23:11:23
title: swoole| swoole 協(xié)程知識點(diǎn)小結(jié)
本文要點(diǎn):
- swoole 協(xié)程現(xiàn)狀一覽: 學(xué)不動? 其實(shí)是更簡單了
- 使用 swoole 協(xié)程很簡單: 開個(gè)協(xié)程, 協(xié)程里寫非阻塞代碼
- 展望 swoole 協(xié)程未來
swoole 協(xié)程現(xiàn)狀一覽
swoole 一直保持著 頗為快速 的迭代速度, 快到什么程度呢 -- 「快別更新了, 學(xué)不動了」
- 半年前還是 v4.0 支持完整的協(xié)程編程(CSP, go+chan), 現(xiàn)在已經(jīng)迭代到 v4.3.3
- v4.3 版本做了一次大更新, 項(xiàng)目拆分成了
swoole
和swoole_async
- 官方 wiki 修改了很多, 協(xié)程的部分的文檔增加了不少, 而且提前到更加顯眼的地方
來一句靈魂叩問: 改動這么大, 那是不是真的 「學(xué)不動了」?
并不是! swoole 是一直在為 世界上最好的語言 添磚加瓦:
- 更為完整的協(xié)程編程支持, 直觀的效果是更加 無縫無感 的編程切換體驗(yàn)(后面細(xì)說), 意味著需要了解和注意的語法細(xì)節(jié)更少, 編程更輕松
- v4.3 版本做的一次大更新, 實(shí)際是優(yōu)化 swoole 項(xiàng)目的架構(gòu), 主項(xiàng)目 focus 協(xié)程模式的網(wǎng)絡(luò)編程, 更多網(wǎng)絡(luò)編程相關(guān)的功能, 使用 擴(kuò)展(ext) 的方式提供(具體可以參考 swoole 的 github 主頁: https://github.com/swoole, 掃一眼下面有的項(xiàng)目, 就能有所啟發(fā))
- 官方 wiki 一直以信息量著稱(同時(shí)也意味著 可以學(xué)到很多東西), 但是如果具備了 網(wǎng)絡(luò)編程 + 協(xié)程 的基礎(chǔ)知識, 然后 focus 到 swoole 協(xié)程部分文檔 上, 就會發(fā)現(xiàn)其實(shí)都是一些 編程語法, so easy~
使用 swole 協(xié)程
如何使用協(xié)程:
- 使用
go()(\Swoole\Coroutine::create() 的簡寫)
創(chuàng)建一個(gè)協(xié)程 - 在 go() 的回調(diào)函數(shù)中, 加入?yún)f(xié)程需要執(zhí)行的代碼, 注意是 非阻塞代碼
use Swoole\Coroutine as Co; // 常用的縮寫方式
go(function () { // 創(chuàng)建協(xié)程, 回調(diào)函數(shù)中寫需要在協(xié)程中執(zhí)行的代碼
echo "daydaygo";
Co::sleep(1); // 不能是阻塞代碼
});
swoole 中協(xié)程就是這么簡單: 開個(gè)協(xié)程, 協(xié)程里寫非阻塞代碼. 官方協(xié)程部分文檔看起來很多, 牢記這兩點(diǎn), 其實(shí)很簡單!
開協(xié)程
- 上文提到的, 使用
go()
創(chuàng)建一個(gè)協(xié)程 - swoole server 中, 底層自動在 onRequet, onReceive, onConnect 等事件回調(diào)之前自動創(chuàng)建一個(gè)協(xié)程
-
開啟
enable_coroutine
參數(shù)后的影響范圍: 主要還包括 Timer 定時(shí)器
-
開啟
- 使用
task_enable_coroutine
開啟的協(xié)程版 Task 進(jìn)程, 會在 onTask 回調(diào)之前自動創(chuàng)建一個(gè)協(xié)程 - 進(jìn)程和進(jìn)程池支持開啟協(xié)程, 開啟后創(chuàng)建的子進(jìn)程會自動創(chuàng)建協(xié)程
// tcp/udp server, 可以在此基礎(chǔ)可封裝 rpc
$s = new \Swoole\Server();
// http server, 替代傳統(tǒng)的 fpm
$s = new \Swoole\Http\Server();
// 開啟 http2 支持: https://wiki.swoole.com/wiki/page/326.html
$s = new \Swoole\Http\Server();
$s->set([
'open_http2_protocol' => true,
]);
// 進(jìn)而可以實(shí)現(xiàn)基于 http2 的服務(wù), 比如 grpc
// websocket server
$s = new \Swoole\WebSocket\Server();
非阻塞代碼
協(xié)程中必須編寫 非阻塞代碼, 看到上面 Co::sleep(1)
的小伙伴會有疑問了:
連個(gè) sleep 都要 swoole 提供一個(gè)協(xié)程版, 我得掌握多少 swoole 協(xié)程版 API 才夠呀?
所以問題的關(guān)鍵點(diǎn)來了:
- 協(xié)程中一定要使用 非阻塞代碼(一定要牢記, 多次重復(fù)了, 可以心里再默念三遍)
- 怎么區(qū)分哪些是阻塞的, 哪些是非阻塞的: 可以參考 官方wiki - runtime
- 隨著 swoole 的迭代, 對協(xié)程的支持越來越完整,
區(qū)分哪些阻塞, 哪些非阻塞
, 越來越無感
swoole 更新后, 添加了開啟協(xié)程 runtime 功能:
// 沒有開啟協(xié)程 runtime, 需要協(xié)程版 API
use Swoole\Coroutine as Co;
go(function () {
echo "daydaygo";
Co::sleep(1); // 需要使用 swoole 提供的協(xié)程版 API
});
// 開啟協(xié)程 runtime
\Swoole\Runtime::enableCoroutine();
go(function () {
echo "daydaygo";
sleep(1); // 和原來編程一樣了
});
協(xié)程 runtime 開啟后, 支持的列表:
- redis擴(kuò)展
- 使用mysqlnd模式的pdo舌缤、mysqli擴(kuò)展匀奏,如果未啟用mysqlnd將不支持協(xié)程化
- soap擴(kuò)展
file_get_contents、fopen
stream_socket_client (predis)
stream_socket_server
-
stream_select
(需要4.3.2以上版本) - fsockopen
-
文件操作 底層使用 AIO 線程池模擬實(shí)現(xiàn)
- fopen / fclose
- fread / fwrite
- fgets / fputs
file_get_contents / file_put_contents
- unlink / mkdir / rmdir
-
sleep系列函數(shù)
- sleep / usleep
time_nanosleep / time_sleep_until
不支持的列表:
- mysql:底層使用libmysqlclient
- curl:底層使用libcurl (即不能使用CURL驅(qū)動的Guzzle)
- mongo:底層使用mongo-c-client
pdo_pgsql / pdo_ori / pdo_odbc / pdo_firebird
協(xié)程 runtime 還不支持怎么辦
需要的功能協(xié)程 runtime 下還沒支持怎么辦? 你有三種方法:
- 協(xié)程 runtime 之前, 官方和社區(qū)已經(jīng)貢獻(xiàn)了很多協(xié)程版 API 可供使用, 比如上面
Co::sleep(1)
, PostgreSQL ClientSwoole\Coroutine\PostgreSQL
- 官方和社區(qū)沒有, 可以使用 swoole 提供的協(xié)程版 client 進(jìn)行封裝, 可以參考 官方 amqp client 封裝, 將 socket() 函數(shù)實(shí)現(xiàn)的 tcp client, 使用 swoole 協(xié)程版 tcp client 實(shí)現(xiàn)即可
public function connect()
{
// 使用 Swoole\Coroutine\Client
$sock = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
if (!$sock->connect($this->host, $this->port, $this->connection_timeout))
{
throw new AMQPRuntimeException(
sprintf(
'Error Connecting to server(%s): %s ',
$sock->errCode,
swoole_strerror($sock->errCode)
),
$sock->errCode
);
}
$this->sock = $sock;
}
PS: 這里只是拋磚引玉, 原庫中使用 stream_socket_client()
, 現(xiàn)在 swoole 協(xié)程 runtime 已經(jīng)支持了
- 傳統(tǒng)的阻塞解決方案(當(dāng)然是在現(xiàn)有的協(xié)程方式都不行, 才會繼續(xù)使用傳統(tǒng)的方式): 拋給 swoole 的 task 進(jìn)程, 使用 MQ 異步掉, 等等
展望 swoole 協(xié)程的未來
到目前為止, 希望小伙伴們已經(jīng) get 到了 swoole 中協(xié)程編程的要點(diǎn)(我喜歡用 姿勢, 人最緊要的是姿勢好看~), 讓我們展望一下未來:
- 解鎖更多協(xié)程使用: chan, defer, select, waitgroup, 這些官方都提供了 demo( 韓天峰 - PHP 協(xié)程:Go + Chan + Defer), 看完后自己也能封裝一份
- 并不是 完整(100%, one hundred percent) 的支持, 要是一不小心踩到不支持的怎么辦? swoole 的后續(xù)版本將支持檢測協(xié)程環(huán)境下是否有阻塞調(diào)用
- 隨著 swoole 官方在協(xié)程編程上的持續(xù)發(fā)力, 基于 swoole 實(shí)現(xiàn)的全協(xié)程式 PHP 開發(fā)框架也將更為簡單, 從基礎(chǔ)/底層的網(wǎng)絡(luò)編程到整個(gè)微服務(wù)架構(gòu)的道路也將更為平坦, 比如馬上將要迎來大版本升級的 swoft2
寫在最后
官方協(xié)程部分的文檔看起來多, 其實(shí)多是對協(xié)程 API 的介紹, 并沒有在知識結(jié)構(gòu)理解的復(fù)雜度上有所增加. swoole 中協(xié)程的編程語法, 都在 \Swoole\Coroutine
命名空間下可以找到.
最后回到一個(gè)經(jīng)典問題, 學(xué)習(xí) swoole 的協(xié)程好, 還是學(xué)習(xí) go 的協(xié)程好? 我談?wù)勎覀€(gè)人的觀點(diǎn):
- 所需要的基礎(chǔ)知識: 網(wǎng)絡(luò)編程 + 協(xié)程, 不會因?yàn)槟闶怯?swoole 還是 go 而有所減少, 基礎(chǔ)不大好, 表現(xiàn)出來了就是學(xué)著學(xué)著就容易卡住, 效率上不來
- 以為你寫的是 swoole, 不不不, 寫的是一個(gè)又一個(gè)功能的 API, go 也同樣(要用到 redis/mysql/mq, 相應(yīng)的 API 你還是得學(xué)得會), 區(qū)別在于, swoole 趨勢是在底層實(shí)現(xiàn)支持(比如 協(xié)程runtime), 這樣 PHPer 可以無縫切換過來, 而 Gopher 則需要學(xué)習(xí)一個(gè)又一個(gè)基于 go 協(xié)程封裝好的 API. 當(dāng)初在 PHP 中學(xué)習(xí)的這些 API, 到 go 里面, 一樣需要再熟悉一遍
- 最后來談?wù)勑阅? 請?jiān)试S我用一個(gè)傲嬌一點(diǎn)的說, 你用 swoole 達(dá)不到的性能, 換個(gè)語言, 呵呵呵. 難易程度排行:
加機(jī)器 < 加程序員 < 加語言
.