Swoole從入門到入土(28)——協程[核心API]


本節專門介紹swoole提供的協程機制中核心的API

 

類方法:

1) set():協程設置,設置協程相關選項。

Swoole\Coroutine::set(array $options);

 

2) getOptions():獲取設置的協程相關選項。

Swoole\Coroutine::getOptions(): null|array;

 

3) create():創建一個新的協程,並立即執行。

Swoole\Coroutine::create(callable $function, ...$args): int|false

go(callable $function, ...$args): int|false // 參考php.ini的use_shortname配置

$function:協程執行的代碼,必須為 callable,系統能創建的協程總數量受限於 server->max_coroutine 設置

返回值:創建失敗返回 false,創建成功返回協程的 ID。

注意:由於底層會優先執行子協程的代碼,因此只有子協程掛起時,Coroutine::create 才會返回,繼續執行當前協程的代碼。在一個協程中使用 go 嵌套創建新的協程。因為 Swoole 的協程是單進程單線程模型,因此,使用 go 創建的子協程會優先執行,子協程執行完畢或掛起時,將重新回到父協程向下執行代碼;如果子協程掛起后,父協程退出,不影響子協程的執行。如下示例:

\Co\run(function() {
    go(function () {
        Co::sleep(3.0);
        go(function () {
            Co::sleep(2.0);
            echo "co[3] end\n";
        });
        echo "co[2] end\n";
    });

    Co::sleep(1.0);
    echo "co[1] end\n";
});

注意:

·每個協程都是相互獨立的,需要創建單獨的內存空間 (棧內存),在 PHP-7.2 版本中底層會分配 8K 的 stack 來存儲協程的變量,zval 的尺寸為 16字節,因此 8K 的 stack 最大可以保存 512 個變量。協程棧內存占用超過 8K 后 ZendVM 會自動擴容。協程退出時會釋放申請的 stack 內存。
·PHP-7.1、PHP-7.0 默認會分配 256K 棧內存
·可調用 Co::set(['stack_size' => 4096]) 修改默認的棧內存尺寸

 

4) defer():defer 用於資源的釋放,會在協程關閉之前 (即協程函數執行完畢時) 進行調用,就算拋出了異常,已注冊的 defer 也會被執行。

Swoole\Coroutine::defer(callable $function);

defer(callable $function); // 短名API

注意:需要注意的是,它的調用順序是逆序的(先進后出), 也就是先注冊 defer 的后執行,先進后出。逆序符合資源釋放的正確邏輯,后申請的資源可能是基於先申請的資源的,如先釋放先申請的資源,后申請的資源可能就難以釋放。

示例:

go(function () {
    defer(function () use ($db) {
        $db->close();
    });
});

 

5) exists():判斷指定協程是否存在。

Swoole\Coroutine::exists(int $cid = 0): bool

示例:

\Co\run(function () {
    go(function () {
        go(function () {
            Co::sleep(0.001);
            var_dump(Co::exists(Co::getPcid())); // 1: true
        });
        go(function () {
            Co::sleep(0.003);
            var_dump(Co::exists(Co::getPcid())); // 3: false
        });
        Co::sleep(0.002);
        var_dump(Co::exists(Co::getPcid())); // 2: false
    });
});

 

 6) getCid():獲取當前協程的唯一 ID, 它的別名為 getuid, 是一個進程內唯一的正整數。

Swoole\Coroutine::getCid(): int

返回值:成功時返回當前協程 ID;如果當前不在協程環境中,則返回 -1

 

 7) getPcid():獲取當前協程的父 ID。 

Swoole\Coroutine::getPcid([$cid]): int

$cid:協程 cid,參數缺省,可傳入某個協程的 id 以獲取它的父 id

示例:

var_dump(Co::getPcid());
\Co\run(function () {
    var_dump(Co::getPcid());
    go(function () {
        var_dump(Co::getPcid());
        go(function () {
            var_dump(Co::getPcid());
            go(function () {
                var_dump(Co::getPcid());
            });
            go(function () {
                var_dump(Co::getPcid());
            });
            go(function () {
                var_dump(Co::getPcid());
            });
        });
        var_dump(Co::getPcid());
    });
    var_dump(Co::getPcid());
});
var_dump(Co::getPcid());

// bool(false)
// int(-1)
// int(1)
// int(2)
// int(3)
// int(3)
// int(3)
// int(1)
// int(-1)
// bool(false)
/*
說明:
非嵌套協程調用 getPcid 將返回 -1 (從非協程空間創建的)
在非協程內調用 getPcid 將返回 false (沒有父協程)
0 作為保留 id, 不會出現在返回值中
*/

注意:協程之間並沒有實質上的持續父子關系,協程之間是相互隔離,獨立運作的,此 Pcid 可理解為創建了當前協程的協程 id

 

8) getContext():獲取當前協程的上下文對象。

Swoole\Coroutine::getContext([$cid]): Swoole\Coroutine\Context

$cid:協程 cid,可選參數;默認返回當前協程的上下文對象。

作用:

· 協程退出后上下文自動清理 (如無其它協程或全局變量引用)
· 無 defer 注冊和調用的開銷 (無需注冊清理方法,無需調用函數清理)
· 無 PHP 數組實現的上下文的哈希計算開銷 (在協程數量巨大時有一定好處)
· Co\Context 使用 ArrayObject, 滿足各種存儲需求 (既是對象,也可以以數組方式操作)

function func(callable $fn, ...$args)
{
    go(function () use ($fn, $args) {
        $fn(...$args);
        echo 'Coroutine#' . Co::getCid() . ' exit' . PHP_EOL;
    });
}

/**
* Compatibility for lower version
* @param object|Resource $object
* @return int
*/
function php_object_id($object)
{
    static $id = 0;
    static $map = [];
    $hash = spl_object_hash($object);
    return $map[$hash] ?? ($map[$hash] = ++$id);
}

class Resource
{
    public function __construct()
    {
        echo __CLASS__ . '#' . php_object_id((object)$this) . ' constructed' . PHP_EOL;
    }

    public function __destruct()
    {
        echo __CLASS__ . '#' . php_object_id((object)$this) . ' destructed' . PHP_EOL;
    }
}

$context = new Co\Context();
assert($context instanceof ArrayObject);
assert(Co::getContext() === null);
func(function () {
    $context = Co::getContext();
    assert($context instanceof Co\Context);
    $context['resource1'] = new Resource;
    $context->resource2 = new Resource;
    func(function () {
        Co::getContext()['resource3'] = new Resource;
        Co::yield();
        Co::getContext()['resource3']->resource4 = new Resource;
        Co::getContext()->resource5 = new Resource;
    });
});
Co::resume(2);

Swoole\Event::wait();

// --EXPECT--
// Resource#1 constructed
// Resource#2 constructed
// Resource#3 constructed
// Coroutine#1 exit
// Resource#2 destructed
// Resource#1 destructed
// Resource#4 constructed
// Resource#5 constructed
// Coroutine#2 exit
// Resource#5 destructed
// Resource#3 destructed
// Resource#4 destructed

 

9) yield():手動讓出當前協程的執行權。而不是基於 IO 的協程調度;此方法擁有另外一個別名:Coroutine::suspend()

Swoole\Coroutine::yield();

注意:必須與 Coroutine::resume() 方法成對使用。該協程 yield 以后,必須由其他外部協程 resume,否則將會造成協程泄漏,被掛起的協程永遠不會執行。

示例:

$cid = go(function () {
    echo "co 1 start\n";
    co::yield();
    echo "co 1 end\n";
});

go(function () use ($cid) {
    echo "co 2 start\n";
    co::sleep(0.5);
    co::resume($cid);
    echo "co 2 end\n";
});
Swoole\Event::wait();

 

10) resume():手動恢復某個協程,使其繼續運行,不是基於 IO 的協程調度。

Swoole\Coroutine::resume(int $coroutineId);

$coroutineId:為要恢復的協程 ID

注意:當前協程處於掛起狀態時,另外的協程中可以使用 resume 再次喚醒當前協程

示例:

$id = go(function(){
    $id = co::getuid();
    echo "start coro $id\n";
    Co::suspend();
    echo "resume coro $id @1\n";
    Co::suspend();
    echo "resume coro $id @2\n";
});
echo "start to resume $id @1\n";
Co::resume($id);
echo "start to resume $id @2\n";
Co::resume($id);
echo "main\n";
Swoole\Event::wait();

// --EXPECT--
// start coro 1
// start to resume 1 @1
// resume coro 1 @1
// start to resume 1 @2
// resume coro 1 @2
// main

 

11) list():遍歷當前進程內的所有協程。

Swoole\Coroutine::list(): Swoole\Coroutine\Iterator

Swoole\Coroutine::listCoroutines(): Swoole\Coroitine\Iterator

返回值:返回迭代器,可使用 foreach 遍歷,或使用 iterator_to_array 轉為數組

示例:

$coros = Swoole\Coroutine::listCoroutines();
foreach($coros as $cid)
{
    var_dump(Swoole\Coroutine::getBackTrace($cid));
}

 

12) stats():獲取協程狀態。

Swoole\Coroutine::stats(): array

返回值:

 示例:

var_dump(Swoole\Coroutine::stats());

array(1) {
  ["c_stack_size"]=>
  int(2097152)
  ["coroutine_num"]=>
  int(132)
  ["coroutine_peak_num"]=>
  int(2)
}

 

13) getBackTrace():獲取協程函數調用棧。

Swoole\Coroutine::getBackTrace(int $cid = 0, int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): array

$cid:協程的 CID,默認當前協程 CID

$options:設置選項,默認值:DEBUG_BACKTRACE_PROVIDE_OBJECT 【是否填充 object 的索引】;其它值:DEBUG_BACTRACE_IGNORE_ARGS 【是否忽略 args 的索引,包括所有的 function/method 的參數,能夠節省內存開銷】

$limit:限制返回堆棧幀的數量

返回值:指定的協程不存在,將返回 false;成功返回數組,格式與 debug_backtrace 函數返回值相同。

示例:

function test1() {
    test2();
}

function test2() {
    while(true) {
        co::sleep(10);
        echo __FUNCTION__." \n";
    }
}
\Co\run(function () {
    $cid = go(function () {
        test1();
    });

    go(function () use ($cid) {
        while(true) {
            echo "BackTrace[$cid]:\n-----------------------------------------------\n";
            //返回數組,需要自行格式化輸出
            var_dump(co::getBackTrace($cid))."\n";
            co::sleep(3);
        }
    });
});
Swoole\Event::wait();

 

14) printBackTrace():打印協程函數調用棧。參數和 getBackTrace 一致。

Swoole\Coroutine::printBackTrace(int $cid = 0, int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0);

 

15) getElapsed():獲取協程運行的時間以便於分析統計或找出僵屍協程

Swoole\Coroutine::getElapsed([$cid]): int

$cid:可選參數,協程的 CID;默認值:當前協程 CID

返回值:協程已運行的時間浮點數,毫秒級精度

 

 

函數:

1) batch():並發執行多個協程,並且通過數組,返回這些協程方法的返回值。

Swoole\Coroutine\batch(array $tasks, float $timeout = -1): array

$tasks:傳入方法回調的數組,如果指定了 key,則返回值也會被該 key 指向

$timeout:總的超時時間,超時后會立即返回。但正在運行的協程會繼續執行完畢,而不會中止

返回值:返回一個數組,里面包含回調的返回值。如果 $tasks 參數中,指定了 key,則返回值也會被該 key 指向

示例:

use Swoole\Coroutine;
use function Swoole\Coroutine\batch;

Coroutine::set(['hook_flags' => SWOOLE_HOOK_ALL]);

$start_time = microtime(true);
Coroutine\run(function () {
    $use = microtime(true);
    $results = batch([
        'file_put_contents' => function () {
            return file_put_contents(__DIR__ . '/greeter.txt', "Hello,Swoole.");
        },
        'gethostbyname' => function () {
            return gethostbyname('localhost');
        },
        'file_get_contents' => function () {
            return file_get_contents(__DIR__ . '/greeter.txt');
        },
        'sleep' => function () {
            sleep(1);
            return true; // 返回NULL 因為超過了設置的超時時間0.1秒,超時后會立即返回。但正在運行的協程會繼續執行完畢,而不會中止。
        },
        'usleep' => function () {
            usleep(1000);
            return true;
        },
    ], 0.1);
    $use = microtime(true) - $use;
    echo "Use {$use}s, Result:\n";
    var_dump($results);
});
$end_time =  microtime(true) - $start_time;
echo "Use {$end_time}s, Done\n";

 

2) parallel():並發執行多個協程。

Swoole\Coroutine\parallel(int $n, callable $fn): void

$n:設置最大的協程數為 $n

$fn:對應需要執行的回調函數

示例 :

use Swoole\Coroutine;
use Swoole\Coroutine\System;
use function Swoole\Coroutine\parallel;

$start_time = microtime(true);
Coroutine\run(function () {
    $use = microtime(true);
    $results = [];
    parallel(2, function () use (&$results) {
        System::sleep(0.2);
        $results[] = System::gethostbyname('localhost');
    });
    $use = microtime(true) - $use;
    echo "Use {$use}s, Result:\n";
    var_dump($results);
});
$end_time =  microtime(true) - $start_time;
echo "Use {$end_time}s, Done\n";

 

3) map():類似於 array_map,為數組的每個元素應用回調函數。

Swoole\Coroutine\map(array $list, callable $fn, float $timeout = -1): array

$list:運行 $fn 函數的數組

$fn:$list 數組中的每個元素需要執行的回調函數

$timeout:總的超時時間,超時后會立即返回。但正在運行的協程會繼續執行完畢,而不會中止

示例:

use Swoole\Coroutine;
use function Swoole\Coroutine\map;

function fatorial(int $n): int
{
    return array_product(range($n, 1));
}

Coroutine\run(function () {
    $results = map([2, 3, 4], 'fatorial'); 
    print_r($results);
});

 

4) deadlock_check():協程死鎖檢測,調用時會輸出相關堆棧信息;默認開啟,在 EventLoop 終止后,如果存在協程死鎖,底層會自動調用;可以通過在 Coroutine::set 中設置 enable_deadlock_check 進行關閉。

Swoole\Coroutine\deadlock_check();

 

 

以上這些就是協程核心API與使用方法了,下一節我們將一起了解swoole為我們帶來的系統API

 

 

---------------------------  我是可愛的分割線  ----------------------------

最后博主借地宣傳一下,漳州編程小組招新了,這是一個面向漳州青少年信息學/軟件設計的學習小組,有意向的同學點擊鏈接,聯系我吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM