swoole| swoole 協程用法筆記

date: 2019-05-01 19:09:34
title: 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);
    }
}

其他

  • 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();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章