php Swoole协程基础

2021-10-17
  1. 优先执行子协程(即go()里面的逻辑),直到发生协程yield(co::sleep 处),然后协程调度到外层协程
  2. 每个协程都是相互独立的,需要创建单独的内存空间 (栈内存);协程退出时会释放申请的stack内存;协程栈内存占用超过后ZendVM会自动扩容。

协程基础使用

use Swoole\Coroutine;
use function Swoole\Coroutine\go;
use function Swoole\Coroutine\run;


// 协程容器,协程代码需要在协程容器中使用
run(function(){
	// 创建协程时传参
	go(function($val1, $val2, $val3){
		echo $val1;
		// 创建协程时不传参
		go(function(){
			// 遍历当前进程内的所有协程,元素为协程id
		    foreach(Coroutine::list() as $cid) {
		    	// 获取指定协程的运行时间,便于分析统计、找出僵尸协程,默认当前协程;毫秒精度
		    	var_dump('协程'.$cid.'的运行时间:'.Coroutine::getElapsed($cid));

		    	// 获取当前PHP栈的内存使用量,默认当前协程,版本4.8.0以上可用
		    	// var_dump('协程'.$cid.'的内存使用量:'.Coroutine::getStackUsage($cid));

		    	// 获取协程函数的调用栈
		    	var_dump(Coroutine::getBackTrace($cid));

		    }


		    // 获取协程状态
		    var_dump(Coroutine::stats());
		    [
		    	'event_num'           => '当前reactor事件数量',
		    	'signal_listener_num' => '当前监听信号的数量',
		    	'aio_task_num'        => '异步IO任务数量 (这里的aio指文件IO或dns, 不包含其它网络IO, 下同)',
		    	'aio_worker_num'      => '异步IO工作线程数量',
		    	'c_stack_size'        => '每个协程的C栈大小',
		    	'coroutine_num'       => '当前运行的协程数量',
		    	'coroutine_peak_num'  => '当前运行的协程数量的峰值',
		    	'coroutine_last_cid'  => '最后创建协程的 id',
		    ];


			Coroutine::sleep(2.0);
			echo 100;
			// 获取指定协程的上下文对象,默认获取当前协程的
			var_dump(Coroutine::getContext(Coroutine::getCid()));

			

		});
		echo $val2;
		echo $val3;
		
	}, 1, 2, 3);

	$a = 100;
	// 用于释放资源,会在协程关闭之前 (即协程函数执行完毕时) 进行调用,就算抛出了异常,已注册的defer也会被执行。
	// 调用顺序是逆序的(先进后出, 也就是先注册defer的后执行,先进后出
	// 示例:例如关联数据库连接
	Coroutine::defer(function () use ($a) {

		// 获取当前协程的唯一ID,进程内唯一的正整数
		var_dump($cid = Coroutine::getCid());

		// 获取指定协程的父ID,不传$cid时获取当前协程的父ID;没有父协程时返回false,在非嵌套协程中使用返回-1
		var_dump($pcid = Coroutine::getPcid($cid));

		// 判断指定协程是否存在
		var_dump(Coroutine::exists($cid));
		var_dump(Coroutine::exists($pcid));


        unset($a);
    });
});

协程上下文对象

  1. 协程退出后上下文自动清理 (如无其它协程或全局变量引用)
  2. 不需要defer注册和调用,省去开销
  3. 无PHP数组实现的上下文的哈希计算开销 (在协程数量巨大时有一定好处)
  4. Context使用ArrayObject, 满足各种存储需求(既是对象,也可以以数组方式操作)
  5. 不需要手动销毁变量
use Swoole\Coroutine;
use function Swoole\Coroutine\go;
use function Swoole\Coroutine\run;
use Swoole\Coroutine\Context;


/**
* 获取对象在当前进程内的唯一ID
* @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 A
{
	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;
    }
}

run(function(){
	go(function(){
		$context = Coroutine::getContext();
		$context['resource1'] = new A;
		$context['resource2'] = new A;
		$context['resource3'] = new A;
		var_dump($context instanceof Context);     // true
		var_dump($context instanceof ArrayObject); // true
		var_dump(Coroutine::getContext());
		go(function(){
			Coroutine::getContext()['resource4'] = new A;
			Coroutine::getContext()['resource5'] = new A;
			var_dump(Coroutine::getContext());
		});
	});
});

并发协程调用

版本4.8.0以上可用

use Swoole\Coroutine;

use function Swoole\Coroutine\go;
use function Swoole\Coroutine\run;

run(function () {
    // join的第一个参数时协程CID数组
    $status = Coroutine::join([
        go(function () use (&$result) {
            $result['baidu'] = strlen(file_get_contents('https://www.baidu.com/'));
        }),
        go(function () use (&$result) {
            $result['google'] = strlen(file_get_contents('https://www.google.com/'));
        })
    ], 1); // 1为总超时时间,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止
    var_dump($result, $status, swoole_strerror(swoole_last_error(), 9));
});

版本4.5.2以上可用

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);
    // 第一个参数为回调方法的数组,可以指定$key,指定时返回值也是以$key为键,函数返回为值;第二个参数为总超时时间,超时后会立即返回。但正在运行的协程会继续执行完毕,而不会中止
    $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";

版本4.5.5以上可用

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

$start_time = microtime(true);
Coroutine\run(function () {
    $use = microtime(true);
    $results = [];

	// 使用$n个协程指定方法
    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";

 

{/if}