🧬PHP的yield到协程直观的理解
This commit is contained in:
parent
25a36d99dc
commit
f93e975f9c
116
_posts/编程/PHP/coroutine/yield-coroutine.md
Normal file
116
_posts/编程/PHP/coroutine/yield-coroutine.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
title: 🧬PHP的yield到协程直观的理解
|
||||
date: 2023-11-01
|
||||
tags: PHP,协程
|
||||
---
|
||||
|
||||
在PHP8.1未发布`fiber`之前,你可能会听到一些前辈说"PHP的`yield`就是协程",但是官方文档对于`yield`是如下解释:
|
||||
|
||||
> `yield`(生成器)提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。
|
||||
> 生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。
|
||||
|
||||
官方的解释自然是正确和科学的,可是你的疑问是这个和协程有什么直接关系呢?
|
||||
|
||||
所以我将从生成器概念讲起到`yield`关键字使用,再到协程概念。或许你会从我的讲解得到一些解惑,当然如有不正之处,欢迎指点!
|
||||
|
||||
## 生成器的概念
|
||||
|
||||
在计算机科学中,生成器是一种特殊的函数,它可以按需生成一系列值,而不需要一次性生成整个序列。比较直观的例子就是汽车厂家是按照订单生产汽车,来一个订单生产一辆。而不是先大批量生产之后再分发销售。这样的好处是厂家节省资源,避免浪费。
|
||||
|
||||
生成器还有一个重要特性是它能够暂停和恢复执行!还是以汽车厂家生产汽车为例子,假设厂家接受了两个汽车订单,一单生产轿车一单生产suv,但是生产suv进行一半流程中发现部分零件需要等待采购。厂家自然会暂停生产suv流程,转而去生产轿车避免生产线停产等待从而浪费资源。待零件采购回来再恢复suv生产流程。
|
||||
|
||||
这种暂停和恢复执行的能力在处理大量数据、遍历复杂的数据结构、实现惰性计算等场景下可以节省大量内存和提高性能。原因它只在需要时才生成值,而不是一次性生成整个序列!
|
||||
|
||||
<!--more-->
|
||||
|
||||
## yield的使用
|
||||
|
||||
在PHP想使用生成器需要使用`yield`关键字进行定义。每次遇到 yield 关键字时,函数会将当前状态保存下来,并返回一个值给调用者。PHP在此外部实现`Iterator`接口的方式,这样我们可以通过`Iterator`接口约定来调度&使用。
|
||||
|
||||
目前常用的有如下方法:
|
||||
|
||||
1. valid() 判断生成器是否可用
|
||||
2. current() 返回当前yield表达式,如果生成器已关闭,则返回null
|
||||
3. send() 设置yield表达式的返回值并恢复生成器
|
||||
|
||||
```php
|
||||
//这边语法需要PHP7.0 以上版本运行
|
||||
function gen(): Generator
|
||||
{
|
||||
$result = yield 'yield1';
|
||||
var_dump('yield1:'.$result);
|
||||
$result = yield 'yield2';
|
||||
var_dump('yield2:'.$result);
|
||||
$result = yield 'yield3';
|
||||
var_dump('yield3:'.$result);
|
||||
}
|
||||
|
||||
$gen = gen();
|
||||
|
||||
var_dump($gen->current()); // 默认返回第一个yield表达式的内容:"yield1"
|
||||
|
||||
var_dump($gen->send(1)); // 恢复yield1,设置yield1表达式返回内容为1 返回下一个yield表达式
|
||||
|
||||
var_dump($gen->send(2));// 恢复yield2,设置yield2表达式返回内容为2 返回下一个yield表达式
|
||||
|
||||
var_dump($gen->send(3));// 恢复yield3,设置yield2表达式返回内容为2 返回下一个yield表达式
|
||||
|
||||
var_dump($gen->valid()); //返回false 因为上一步所有yield已经全部执行完了,生成器已关闭
|
||||
|
||||
```
|
||||
|
||||
**因为生成器按需生成一系列值,所以不可逆变,也是恢复yield1之后只能往下走!**
|
||||
|
||||
到此这一步在回味官方的例子是否加深理解?
|
||||
|
||||
## 协程概念
|
||||
|
||||
协程(Coroutine)是一种轻量级的并发执行模型,可以在一个线程内实现多个任务的交替执行。
|
||||
|
||||
1. 并发执行:协程是一种轻量级的并发执行模型,可以在一个线程内实现多个任务的交替执行。与传统的多线程或多进程模型相比,协程的切换开销更小,并且可以充分利用单个线程的资源。
|
||||
2. 非抢占式调度:协程采用非抢占式调度,即任务之间主动让出执行权,而不是由调度器强制切换。这种方式可以更好地控制任务的执行顺序和协作方式,提高效率和灵活性。
|
||||
3. 状态保存和恢复:协程能够在执行过程中保存当前的状态,并在需要时恢复到之前的状态。这使得协程可以在执行过程中暂停和恢复,实现更灵活的任务切换和状态管理。
|
||||
4. 协程通信:协程之间可以进行通信和数据交换,实现协作式的任务处理。协程通信可以通过共享变量、消息传递等方式实现,用于协调任务之间的交互和协作。
|
||||
|
||||
**举个例子:**
|
||||
煮泡面,假设我们把煮泡面,分为两个任务:
|
||||
|
||||
1. 任务一 拆方便面包,放入碗中 耗时:1min
|
||||
2. 任务二 烧开水 耗时:5min
|
||||
|
||||
如果按照常规,需要6min之后我们才能开始泡面。如果换成协程,那么执行逻辑应该如下:
|
||||
|
||||
1. 拆方便面包,放入碗中 这个一步进行5s 暂停
|
||||
2. 切换到烧开水这个一步进行5s 暂停回到1
|
||||
3. 上述1和2两个任务不停来回切换直到任务完成(计算内部切换很快所以开起来两个任务在并发执行)
|
||||
|
||||
按照这样逻辑回到计算机层面上面,协程的并发执行,非抢占式调度,状态保存和恢复是否更好的理解。同样也能解释在协程里面不能出现阻塞进程操作,否则协程退化成传统同步阻塞一样。
|
||||
|
||||
再者现在cpu大多是多核,意味着同一个时刻可以处理多个任务,那么协程的优势更加明显!
|
||||
|
||||
## 还需很多
|
||||
|
||||
所以开始讲的生成器具有特性+加上一个任务调度器不就实现基本的协程的吗?
|
||||
|
||||
1. 生成器,负责任务生成暂停和切换
|
||||
2. 任务调度器,负责任务切换和状态管理以及任务之间的通信和数据交换
|
||||
|
||||
鸟哥一篇博文[在PHP中使用协程实现多任务调度](https://www.laruence.com/2015/05/28/3038.html)基于生成器,附加任务,调度器实现异步非阻塞tcp服务器!整体文章篇幅很大,我还是建议大家可以品味一下任务,调度器那部分实现细节!
|
||||
|
||||
## fiber与yield(Generator)比较
|
||||
|
||||
**fiber** 是在 PHP8.1 中引入的扩展,它提供了一种更高效和灵活的协程模型,可以实现更细粒度的协程调度和协程间的通信。
|
||||
|
||||
生成器没有栈,所以鸟哥博文中设计了任务和调度器,还要设计它们之前如何通信(原文使用yield关键字配合send来通信)!
|
||||
|
||||
Fiber是拥有自己的调用栈,并允许内部任意位置暂停也不需要指定返回类型。这就比用 yield 一样需要返回一个 Generator 实例,清晰明了多了。
|
||||
|
||||
Fiber可以使用 Fiber::resume() 传递任意值、或者使用 Fiber::throw() 向纤程抛出一个异常以恢复运行。
|
||||
|
||||
所以与 yield 相比,fiber 具有以下区别和优势:
|
||||
|
||||
1. 更高效的协程调度:fiber 使用底层的协程调度机制,可以在更细粒度的级别上进行协程切换,从而提供更高效的协程调度和执行。
|
||||
2. 更灵活的协程控制:fiber 允许在协程之间手动进行切换,而不需要依赖生成器函数中的 yield 关键字。这使得协程的控制更加灵活,可以根据需要在任何时候切换协程。
|
||||
3. 更强大的协程通信:fiber 提供了更强大的协程通信机制,可以在协程之间传递数据和消息,实现更复杂的协程间交互和协作。
|
||||
|
||||
但是截止本博文发稿之前,fiber目前还远远达不到类似swoole的协程开箱即用的程度,调度器实现仍然需要开发者自己实现!如果想快速简单使用协程建议使用swoole!
|
Loading…
Reference in New Issue
Block a user