swoole| swoole 協程用法筆記
協程方法一覽
協程方法簡明筆記:
-
協程設定Coroutine::set
-
max_coroutine
-
-
協程狀态Coroutine::stats
-
周遊目前程序中的所有協程Coroutine::list
-
檢視協程調用棧Coroutine::getBackTrace
- 别名
Coroutine::listCoroutines()
- 别名
-
建立協程Coroutine::create
- 短名
go()
- 協程開銷: 推薦使用 php>=7.2, 建立初始配置設定的棧記憶體更小, 并且會自動擴容
- 短名
-
類似 go 的 defer, PHP 有析構函數(Coroutine::defer
)和自動回收機制, defer 的使用範圍沒那麼大__destruct()
-
defer()
-
-
類似 go 的 chan, 所有操作均為記憶體操作, 程序間記憶體隔離Coroutine\Channel
-
chan()
- 初始化是需要設定容量(capacity), 通道
會影響到後續的 push / pop空/滿
- 必須在 swoole server 的 onWorkerStart 之後建立
-
-
目前協程id, 即 cidCoroutine::getCid
-
Coroutine::exist
-
擷取父協程cidCoroutine::getPcid
- 協程的嵌套會帶來初始的先後順序(父子關系), 最終執行還是要看協程的排程(沒有穩定的父子關系)
-
Coroutine::getContext
-
讓出目前協程的執行權, 需要配合 resume 使用, 由其他協程喚醒Coroutine::yield
-
Coroutine::suspend
-
-
喚醒其他協程Coroutine::resume
-
協程版Coroutine::exec
shell_exec()
-
DNS查詢, todo -> http client 是否需要Coroutine::gethostbyname
-
Coroutine::getaddrinfo
-
擷取檔案系統資訊(目前還不知道使用場景)Coroutine::statvfs
-
Coroutine\Client
-
Coroutine\Http\Client
-
Coroutine\Http2\Client
-
Coroutine\Socket
-
Coroutine\PostgreSQL
- 安裝 swoole 時, 需要加編譯參數
--enable-coroutine-postgresql
- 系統需要安裝 libpg 庫
- 安裝 swoole 時, 需要加編譯參數
開啟協程 runtime 後(
\Swoole\Runtime::enableCoroutine()
), 可以不再使用:
-
Coroutine::fread
-
Coroutine::fgets
-
Coroutine::fwrite
-
Coroutine::sleep
-
Coroutine::readFile
-
Coroutine::writeFile
-
使用 ext-redis(phpredis) / predisCoroutine\Redis
-
使用 mysqlnd 模式的 pdo、mysqli 擴充Coroutine\MySQL
阻塞代碼檢驗
swoole 中使用協程的 2 個要點:
- 開協程: 這個容易,
一下就行了go()
- 協程中執行 非阻塞代碼: 除了看官方文檔, 下面提供一個簡單的檢測 demo
go(function () {
sleep(1); // 未開啟協程 runtime, 此處會阻塞, 輸出為 go -> main
echo "go \n";
});
echo "main \n";
輸出為:
go -> main
\Swoole\Runtime::enableCoroutine();
go(function () {
sleep(1); // 開啟協程 runtime, 此處為阻塞, 輸出為 main -> go
echo "go \n";
});
echo "main \n";
main -> go
, 發生了協程排程.
使用時将
sleep(1)
替換為需要檢測的代碼即可.
對短名稱的個人看法
- 建議關閉, 全部使用
命名空間保持一緻性, 按需封裝常用的幾個, 比如\Swoole\Coroutine
go() chan() defer()
- ini 配置: swoole.use_shortname = 'Off'
if (! function_exists('go')) {
function go(callable $callable)
{
\Hyperf\Utils\Coroutine::create($callable);
}
}
if (! function_exists('defer')) {
function defer(callable $callable): void
{
\Hyperf\Utils\Coroutine::defer($callable);
}
}
其他
- swoole 協程中比較重要的參數設定
, 更科學方式(看是否有需要): 壓測後檢視記憶體占用, 進而進行調整max_coroutine
- wiki - 協程程式設計須知 : 使用協程的注意事項
- 協程 go+chan+defer : chan可以用在協程間互動, defer使用場景有待收集
- 實作 defer :
__destruct()
- 實作 waitgroup : chan+count計數, 可以進一步封裝成更友善的寫法
- 版本更新記錄 : 仔細看看, 就能體會到開發組在php 協程上的努力
并發調用
官方提供了 2 種方式:
- setDefer 機制: 延遲收包, 多個請求并發收取相應結果; 確定協程元件支援 setDefer 機制(絕大部分協程元件都支援)
- 子協程 + chan: 每個請求在子協程中執行, 通過 chan 實作請求收包時的協程排程
個人不建議使用, 并發調用可以達到更好的性能, 但是不符合常用的程式設計習慣, 多個請求需要同時完成, 推薦使用
waitGroup
, 在此基礎上, 還可以封裝成更簡單的寫法
class WaitGroup
{
/**
* @var int
*/
private $counter = 0;
/**
* @var SwooleChannel
*/
private $channel;
public function __construct()
{
$this->channel = new chan();
}
public function add(int $incr = 1): void
{
$this->counter += $incr;
}
public function done(): void
{
$this->channel->push(true);
}
public function wait(): void
{
while ($this->counter--) {
$this->channel->pop();
}
}
}