协程
基本
一个请求就是一个协程,路由绑定的控制器方法,已经在协程内了
public function index()
{
if (Coroutine::inCoroutine()){ # true
return Coroutine::id(); # int
};
}
协程 ID
一直递增,跟配置中 Constant::OPTION_MAX_COROUTINE
无关
# OPTION_MAX_COROUTINE => 100
for ( $i=0 ; $i < 10000 ; $i++){
co(function (){
echo Coroutine::id().PHP_EOL; # 10002...
});
}
可以在任何协程中创建协程,称为子协程。主协程可以阻塞等待子协程执行完毕。但实际上他们没有父子关系,协程之间都是同级的,主协程提前结束,不会影响子协程。协程之间的通信由channel
完成
没有等待子协程,直接返回
co(function (){
sleep(2);
});
return ['ok'];
WaitGroup
阻塞等待子协程
use Hyperf\Utils\WaitGroup;
$w = new WaitGroup();
$w->add(1); # 需要指定有几个协程需要等待
co(function ()use($w){
sleep(2);
$w->done();
});
# 设置超时时间。子任务全部成功完成 返回 1
$rs = $w->wait(1);
if ($rs){
return ['ok'];
}
return ['err']; # 只等待了1秒
Parallel
使用WaitGroup
特性封装,可以限制Parallel
内协程最大同时运行数
$p = new Parallel(1); // Parallel 内协程最大同时运行数
$p->add(function (){ // 使用 add 创建协程
sleep(3);
return Coroutine::id();
});
$p->add(function (){
sleep(3);
return Coroutine::id();
});
$rs $p->wait(); // 返回协程内返回的结果 数组
------
$result = parallel([
function () {
sleep(1);
return Coroutine::id();
},
function () {
sleep(1);
return Coroutine::id();
}
]);
Channel
主要用于协程间通讯,生产者和消费者模型
# 实例化时可设置队列(size)长度,默认为 1,可 push 1条数据
$c = new Channel(1);
$c->push($rs); # push 成功返回 1
# 设置消费者超时时间
$c->pop(2);
文档:
- Channel->push :当队列中有其他协程正在等待 pop 数据时,自动按顺序唤醒一个消费者协程。当队列已满时自动 yield 让出控制权,等待其他协程消费数据
- Channel->pop :当队列为空时自动 yield,等待其他协程生产数据。消费数据后,队列可写入新的数据,自动按顺序唤醒一个生产者协程。
为了上面两点,我进了好大的坑。
两个协程,一个生产一个消费,队列为2,生产6个,先生产后消费
$c = new Channel(2);
co(function () use ($c){
for ( $i=0; $i<6; $i++ ){
echo "开始PUSH".PHP_EOL;
$c->push(1);
}
});
co(function ()use($c){
while(true){
echo "开始消费".PHP_EOL;
$rs = $c->pop(2);
if (!$rs){
echo "超时结束".PHP_EOL;
break;
}
}
});
运行结果&日志整理
第二个例子 先消费,后生产
$c = new Channel(2);
co(function ()use($c){
while(true){
echo "开始消费".PHP_EOL;
$rs = $c->pop(2);
if (!$rs){
echo "超时结束".PHP_EOL;
break;
}
}
});
co(function () use ($c){
for ( $i=0;$i<6 ;$i++){
echo "开始PUSH".PHP_EOL;
$c->push(1);
}
});
运行结果&日志整理
Concurrent
控制一个代码块内同时运行的最大协程数。基于Channel
use Hyperf\Utils\Coroutine\Concurrent;
$concurrent = new Concurrent(10);
for ($i = 0; $i < 15; ++$i) {
$concurrent->create(function () {
// Do something...
});
}
协程上下文
在协程结束时,对应的上下文也会自动跟随释放掉
use Hyperf\Utils\Context;
$foo = Context::set('foo', default);
$foo = Context::get('foo', default);
$foo = Context::has('foo');
// 从协程上下文取出 $request 对象并设置 key 为 foo 的 Header,然后再保存到协程上下文中
$request = Context::override(ServerRequestInterface::class, function (ServerRequestInterface $request) {
return $request->withAddedHeader('foo', 'bar');
});
使用 Context
为啥要使用上下文?什么时候用上下文?
目前不是很清楚。文档中提到的几个点
- 对每个协程的状态处理都需要通过
协程上下文
来管理。 不能通过全局变量储存状态
- $_开头的变量、global 变量,以及 static 静态属性
- 状态 可直接理解为会随着请求而变化的值,事实上在 协程 编程中,这些状态值也是应该存放于 协程上下文 中的
- 对于与请求相关的数据在协程下也是需要储存到 协程上下文(Context) 内的,所以在编写代码时请务必注意 不要 将单个请求相关的数据储存在类属性内,包括非静态属性。
实践
- 如果要
保持
请求中数据的修改,需要把修改后的内容写回上下文中的请求接口类上,直接修改是clone
的对象