协程
不需要操作系统参与,创建销毁和切换的成本非常低,遇到io会自动让出cpu执行权,交给其它协程去执行。
Swoole协程
非协程代码
<?php $start = time(); for ($i = 0; $i < 500; $i++) { file_get_contents('http://www.easyswoole.com/'); echo '任务' . $i . '完成' . PHP_EOL; } echo '非协程总耗时' . (time() - $start) . 's' . PHP_EOL;
执行结果:
任务495完成 任务496完成 任务497完成 任务498完成 任务499完成 非协程总耗时13s
协程代码
<?php $start = time(); for ($i = 0; $i < 500; $i++) { Swoole\Coroutine::create(function () use ($i, $start) { $client = new Swoole\Coroutine\Http\Client('www.easyswoole.com', 80); $client->get('/'); echo '任务' . $i . '完成' . '耗时' . (time() - $start) . 's' . PHP_EOL; }); }
执行结果:
任务389完成耗时1s 任务395完成耗时1s 任务434完成耗时1s 任务477完成耗时1s 任务469完成耗时1s 任务385完成耗时1s 任务498完成耗时1s
可以发现速度相当快,但任务的id,不是顺序执行的,这就是遇到了io,swoole底层自动切换让出cpu执行权。
Channle
用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。
执行下面代码:
$start = time(); function task1() { Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发短信\n"; } function task2() { Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发邮件\n"; } Swoole\Coroutine::create('task1'); Swoole\Coroutine::create('task2'); echo '总耗时' . (time() - $start) . 's' . PHP_EOL;
执行结果:
总耗时0s 发短信 发邮件
却发现以上代码,先执行的echo '总耗时' . (time() - $start) . 's' . PHP_EOL;。
要等待task1及task2执行成功后输出,该怎么半呢,这就利用了channel,来实现csp并发编程。
代码:
<?php Swoole\Coroutine::create(function (){ $start = time(); $channel = new Swoole\Coroutine\Channel(); function task1($channel) { /** @var Swoole\Coroutine\Channel $channel */ Swoole\Coroutine::sleep(3); // 模拟io阻塞 $channel->push("发短信\n"); } function task2($channel) { /** @var Swoole\Coroutine\Channel $channel */ Swoole\Coroutine::sleep(3); // 模拟io阻塞 $channel->push("发邮件\n"); } Swoole\Coroutine::create('task1', $channel); Swoole\Coroutine::create('task2', $channel); for ($i = 0; $i < 2; $i++) { echo $channel->pop(); } echo '总耗时' . (time() - $start) . 's' . PHP_EOL; });
执行结果:
发短信 发邮件 总耗时3s
可以看到耗时3s,但我们在增加一个任务,for里面的$i就要修改,使得我们的代码非常繁琐,所以就有了WaitGroup。
像channel可以实现协程通信,依赖管理,协程同步。
实现连接池功能可以看我之前的文章,传送门
WaitGroup
基于Channel实现的Golang的sync.WaitGrup功能。
方法:
- add 方法增加计数
- done 表示任务已完成
- wait 等待所有任务完成恢复当前协程的执行
- WaitGroup 对象可以复用,add、done、wait 之后可以再次使用
代码:
<?php Swoole\Coroutine::create(function () { $start = time(); $waitGroup = new Swoole\Coroutine\WaitGroup(); function task1($waitGroup) { /** @var WaitGroup $waitGroup */ Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发短信\n"; $waitGroup->done();; } function task2($waitGroup) { /** @var WaitGroup $waitGroup */ Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发邮件\n"; $waitGroup->done(); } $waitGroup->add(); Swoole\Coroutine::create('task1', $waitGroup); $waitGroup->add(); Swoole\Coroutine::create('task2', $waitGroup); $waitGroup->wait(); echo '总耗时' . (time() - $start) . 's' . PHP_EOL; });
执行结果跟之前一样,也是好时3s,但是不是更简单了呢。
Context
协程原有的异步逻辑同步化,但是在协程切换是隐式发生的,所有协程切换的前后不能保证全局遍历及static变量的一致性。
用context用协程id做隔离,来保存上下文内容。
代码复现:
<?php class Email { static $email = null; } Swoole\Coroutine::create(function () { function task1($email) { Email::$email = $email; Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发邮件:" . Email::$email . PHP_EOL; } function task2($email) { Email::$email = $email; echo "发邮件:" . Email::$email . PHP_EOL; } Swoole\Coroutine::create('task1', '975975398@gmail.com'); Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com'); });
从感觉啥觉得会输出两个邮箱地址,但其实:
发邮件:gaobinzhan@gmail.com 发邮件:gaobinzhan@gmail.com
这就是变量生命周期,需要注意,我们可以封装一个类来保存上下文。
<?php class Context { /** * [ * 'cid' => [ // 就是协程的id * 'key' => 'value' // 保存的全局变量的信息 * ] * ] * @var [type] */ public static $pool = []; static function get($key) { $cid = Swoole\Coroutine::getuid();// 获取当前运行的协程id if ($cid < 0) { return null; } if (isset(self::$pool[$cid][$key])) { return self::$pool[$cid][$key]; } return null; } static function put($key, $item) { $cid = Swoole\Coroutine::getuid();// 获取当前运行的协程id if ($cid > 0) { self::$pool[$cid][$key] = $item; } } static function delete($key = null) { $cid = Swoole\Coroutine::getuid(); if ($cid > 0) { if ($key) { unset(self::$pool[$cid][$key]); } else { unset(self::$pool[$cid]); } } var_dump(self::$pool); } }
运行以下代码:
<?php Swoole\Coroutine::create(function () { function task1($email) { Context::put('email',$email); Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发邮件:" . Context::get('email') . PHP_EOL; } function task2($email) { Context::put('email',$email); echo "发邮件:" . Context::get('email') . PHP_EOL; } Swoole\Coroutine::create('task1', '975975398@gmail.com'); Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com'); var_dump(Context::$pool); });
运行结果:
发邮件:gaobinzhan@gmail.com array(2) { [2]=> array(1) { ["email"]=> string(19) "975975398@gmail.com" } [3]=> array(1) { ["email"]=> string(20) "gaobinzhan@gmail.com" } } 发邮件:975975398@gmail.com
可以看到,两个邮箱都输出成功了,但是我们的变量没有销毁,如何销毁呢,Swoole提供了defer方法,在协程关闭之前会调用defer。
<?php Swoole\Coroutine::create(function () { function task1($email) { Context::put('email',$email); Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发邮件:" . Context::get('email') . PHP_EOL; Swoole\Coroutine::defer(function (){ Context::delete(); }); } function task2($email) { Context::put('email',$email); echo "发邮件:" . Context::get('email') . PHP_EOL; Swoole\Coroutine::defer(function (){ Context::delete(); }); } Swoole\Coroutine::create('task1', '975975398@gmail.com'); Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com'); });
运行结果:
发邮件:gaobinzhan@gmail.com array(1) { [2]=> array(1) { ["email"]=> string(19) "975975398@gmail.com" } } 发邮件:975975398@gmail.com array(0) { }
可以发现到最后为空,已经被清空掉了。
是不是觉得这样写很麻烦,以及不确定在什么时候销毁,然而Swoole提供的Context可以让协程退出后上下文自动清理 (如无其它协程或全局变量引用)。
代码实现:
<?php Swoole\Coroutine::create(function () { function task1($email) { $context = Swoole\Coroutine::getContext(); $context->email = $email; Swoole\Coroutine::sleep(3); // 模拟io阻塞 echo "发邮件:" . $context->email . PHP_EOL; } function task2($email) { $context = Swoole\Coroutine::getContext(); $context->email = $email; echo "发邮件:" . $context->email . PHP_EOL; } Swoole\Coroutine::create('task1', '975975398@gmail.com'); Swoole\Coroutine::create('task2', 'gaobinzhan@gmail.com'); });
扫码二维码 获取免费视频学习资料
- 本文固定链接: http://phpxs.com/post/7344/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料