欢迎来到「我是真的狗杂谈世界」,关注不迷路 前言 很早就听说PHP8.1出了Fiber(又称纤程),但一直也没时间捣鼓它, 正好前段时间在整理php的新特性/功能,想看看有没有什么可以给日常开发带来便
欢迎来到「我是真的狗杂谈世界」,关注不迷路
很早就听说PHP8.1出了Fiber(又称纤程),但一直也没时间捣鼓它,
正好前段时间在整理php的新特性/功能,想看看有没有什么可以给日常开发带来便利、安全、性能提升的,再看到它感觉跟性能有点关系,
于是就决定捣鼓一下,整理记录下捣鼓过程。
作为开发者(语言使用用户),肯定先感受一下怎么用。按照官方的demo跑了一下:
$fiber = new Fiber(function (): void { echo "我是第二个输出\n"; Fiber::suspend(); echo "我是第四个输出\n";});echo "我是第一个输出\n";$fiber->start();echo "我是第三个输出\n";$fiber->resume();echo "我是第五个输出\n";
我是第一个输出我是第二个输出我是第三个输出我是第四个输出我是第五个输出
看输出顺序不出所料,看demo的意思应该还可以双向传递数据:
$fiber = new Fiber(function (string $fruit1, string $fruit2): void { echo "我是第二个输出;混合水果为:{$fruit1}, {$fruit2}\n"; $amount = Fiber::suspend("{$fruit1}{$fruit2}汁"); echo "我是第四个输出;收银为:{$amount}\n"; Fiber::suspend($amount - 23.5);});echo "我是第一个输出\n";$juice = $fiber->start("苹果", "西瓜");echo "我是第三个输出;果汁为:{$juice}\n";$change = $fiber->resume(50);echo "我是第五个输出;找零为:{$change}\n";
我是第一个输出我是第二个输出;混合水果为:苹果, 西瓜我是第三个输出;果汁为:苹果西瓜汁我是第四个输出;收银为:50我是第五个输出;找零为:26.5
好家伙,看起来是挺新鲜的,但是不是立马想到了PHP5中就支持的生成器+yield(具体生成器+yield的使用说明参考「Chapter 3.PHP5 生成器与yield及原理浅析」
)呢?
于是带着疑问阅读了一下官方文档,果然有写两者的区别:
可以在无需改造中间函数模拟构建调用堆栈的前提下,在任意位置进行Fiber控制,而这在之前的生成器+yield的实现下是无法做到的
这也是之前PHP界一些协程框架一直被诟病之处,必须通过层层嵌套来模拟(模拟方式参考「Chapter 3.PHP5 生成器与yield及原理浅析」
尝试一下(Fiber匿函中调用otherFunc时无需像yield那样就行改造,就像正常函数调用一样就可以):
$fiber = new Fiber(function (): void { otherFunc();});echo "我要开始咯\n";$juice = $fiber->start();echo "另一个函数成功暂停了Fiber;现在尝试恢复Fiber\n";$fiber->resume();function otherFunc(): void{ echo "Fiber已经进入了另一个函数;现在在这里尝试暂停该Fiber\n"; Fiber::suspend(); echo "Fiber已经恢复,我也结束自己的生命周期了\n";}
我要开始咯Fiber已经进入了另一个函数;现在在这里尝试暂停该Fiber另一个函数成功暂停了Fiber;现在尝试恢复FiberFiber已经恢复,我也结束自己的生命周期了
知道了可以这样用,总是想要再进一步了解下为什么Fiber可以这么用,而生成器+yield却不行呢?
带着这个问题开始下一个环节:
继续在官方文档中寻找蛛丝马迹,发现其实在同一段话中已经把原因也说清楚了~~
原因就是:
生成器的执行过程参考「Chapter 3.PHP5 生成器与yield及原理浅析」
基于Fiber具备独立执行堆栈的前提,不妨以下方代码为例猜想一下Fiber大致执行过程(先不管双向值传递):
$fiber = new Fiber(function (): void { otherFunc();});echo "我要开始咯\n";$juice = $fiber->start();echo "另一个函数成功暂停了Fiber;现在尝试恢复Fiber\n";$fiber->resume();function otherFunc(): void{ echo "Fiber已经进入了另一个函数;现在在这里尝试暂停该Fiber\n"; Fiber::suspend(); echo "Fiber已经恢复,我也结束自己的生命周期了\n";}
图中(下图同)内存只展示用户空间几个主要区块,并且为了更简单展示Fiber的执行过程,内存、CPU以及ZendVM屏蔽和抽象了一些细节,如需更多了解细节,可参考以下文章:
$fiber = new Fiber(function (): void { otherFunc();});
当CPU以主线程栈区为执行堆栈执行到002行时:
$juice = $fiber->start();
CPU继续以主线程栈区为执行堆栈执行了005行,执行到006行时:
CPU以Fiber栈区为执行堆栈执行003行,调用otherFunc函数,函数调用的过程参考「Chapter 4. 程序执行过程中的执行堆栈」
Fiber::suspend();
CPU以Fiber栈区为执行堆栈执行012行,执行到013行时:
$fiber->resume();
CPU以主线程栈区为执行堆栈执行007行,执行到008行时(同上述 Fiber的启动过程):
CPU以Fiber栈区为执行堆栈执行014行,otherFuc函数返回(栈帧出栈),回到004行,Fiber终结时:
以上过程只是猜测,为了验证这个猜测的正确性与正确度,带着三脚猫的C记忆来看一下Fiber的源码实现:
先找到几个主要文件中的主要代码块(还好PHP源码很容易就能找到这俩文件):
为Fiber设置了初始化、运行中、暂停、死亡四种状态:
typedef enum {ZEND_FIBER_STATUS_INIT,ZEND_FIBER_STATUS_RUNNING,ZEND_FIBER_STATUS_SUSPENDED,ZEND_FIBER_STATUS_DEAD,} zend_fiber_status;
每个Fiber的上下文结构中维护了该Fiber的函数入口、执行堆栈、状态等信息
struct _zend_fiber_context {void *handle;void *kind;zend_fiber_coroutine function;zend_fiber_clean cleanup;zend_fiber_stack *stack;zend_fiber_status status;zend_execute_data *top_observed_frame;void *reserved[ZEND_MAX_RESERVED_RESOURCES];};
一个Fiber结构中包含了自身、调用者、恢复目标的上下文,执行堆栈当前栈底帧。看起来不是独立一块空间维护全部上下文,而是自维护并组成链表
struct _zend_fiber {zend_object std;uint8_t flags;zend_fiber_context context;zend_fiber_context *caller;zend_fiber_context *previous;zend_fcall_info fci;zend_fcall_info_cache fci_cache;zend_execute_data *execute_data;zend_execute_data *stack_bottom;zend_vm_stack vm_stack;zval result;};
为了方便阅读,源码中仅留下重要代码,前后用代替了:
Fiber::start
、Fiber::resume
方法内部指向zend_fiber_resume
函数Fiber::suspend
方法内部指向zend_fiber_suspend
函数ZEND_METHOD(Fiber, start){fiber->previous = &fiber->context;zend_fiber_transfer transfer = zend_fiber_resume(fiber, NULL, false); }ZEND_METHOD(Fiber, suspend){zend_fiber_transfer transfer = zend_fiber_suspend(fiber, value);}ZEND_METHOD(Fiber, resume){zend_fiber_transfer transfer = zend_fiber_resume(fiber, value, false);}
zend_fiber_resume
、zend_fiber_suspend
函数内部指向zend_fiber_switch_to
函数(顾名思义进行fiber切换)static zend_always_inline zend_fiber_transfer zend_fiber_resume(zend_fiber *fiber, zval *value, bool exception){zend_fiber_transfer transfer = zend_fiber_switch_to(fiber->previous, value, exception);}static zend_always_inline zend_fiber_transfer zend_fiber_suspend(zend_fiber *fiber, zval *value){return zend_fiber_switch_to(caller, value, false);}
zend_fiber_switch_to
函数内部指向zend_fiber_switch_context
static zend_always_inline zend_fiber_transfer zend_fiber_switch_to(zend_fiber_context *context, zval *value, bool exception) {zend_fiber_switch_context(&transfer);}
static zend_object *zend_fiber_object_create(zend_class_entry *ce){zend_fiber *fiber = emalloc(sizeof(zend_fiber));memset(fiber, 0, sizeof(zend_fiber));}
到了这里,可以知道猜测跟实现略有出入,但基本差不多。
最后贴一个Fiber和Swoole的瓜,许久没关注,错过了刀光剑影~
来源地址:https://blog.csdn.net/u011757697/article/details/128256823
--结束END--
本文标题: Chapter 2.PHP8.1 新特性fiber及原理浅析
本文链接: https://www.lsjlt.com/news/387818.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0