天天看點

swoole| swoole 協程用法筆記協程方法一覽阻塞代碼檢驗對短名稱的個人看法其他

swoole| swoole 協程用法筆記

協程方法一覽

協程方法簡明筆記:

  • Coroutine::set

    協程設定
    • max_coroutine

  • Coroutine::stats

    協程狀态
  • Coroutine::list

    周遊目前程序中的所有協程
  • Coroutine::getBackTrace

    檢視協程調用棧
    • 别名

      Coroutine::listCoroutines()

  • Coroutine::create

    建立協程
    • 短名

      go()

    • 協程開銷: 推薦使用 php>=7.2, 建立初始配置設定的棧記憶體更小, 并且會自動擴容
  • Coroutine::defer

    類似 go 的 defer, PHP 有析構函數(

    __destruct()

    )和自動回收機制, defer 的使用範圍沒那麼大
    • defer()

  • Coroutine\Channel

    類似 go 的 chan, 所有操作均為記憶體操作, 程序間記憶體隔離
    • chan()

    • 初始化是需要設定容量(capacity), 通道

      空/滿

      會影響到後續的 push / pop
    • 必須在 swoole server 的 onWorkerStart 之後建立
  • Coroutine::getCid

    目前協程id, 即 cid
  • Coroutine::exist

  • Coroutine::getPcid

    擷取父協程cid
    • 協程的嵌套會帶來初始的先後順序(父子關系), 最終執行還是要看協程的排程(沒有穩定的父子關系)
  • Coroutine::getContext

  • Coroutine::yield

    讓出目前協程的執行權, 需要配合 resume 使用, 由其他協程喚醒
    • Coroutine::suspend

  • Coroutine::resume

    喚醒其他協程
  • Coroutine::exec

    協程版

    shell_exec()

  • Coroutine::gethostbyname

    DNS查詢, todo -> http client 是否需要
  • Coroutine::getaddrinfo

  • Coroutine::statvfs

    擷取檔案系統資訊(目前還不知道使用場景)
  • Coroutine\Client

  • Coroutine\Http\Client

  • Coroutine\Http2\Client

  • Coroutine\Socket

  • Coroutine\PostgreSQL

    • 安裝 swoole 時, 需要加編譯參數

      --enable-coroutine-postgresql

    • 系統需要安裝 libpg 庫

開啟協程 runtime 後(

\Swoole\Runtime::enableCoroutine()

), 可以不再使用:

  • Coroutine::fread

  • Coroutine::fgets

  • Coroutine::fwrite

  • Coroutine::sleep

  • Coroutine::readFile

  • Coroutine::writeFile

  • Coroutine\Redis

    使用 ext-redis(phpredis) / predis
  • Coroutine\MySQL

    使用 mysqlnd 模式的 pdo、mysqli 擴充

阻塞代碼檢驗

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);
    }
}           

其他

并發調用

官方提供了 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();
        }
    }
}