文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Chapter 2.PHP8.1 新特性fiber及原理浅析

2023-09-01 08:29

关注

欢迎来到「我是真的狗杂谈世界」,关注不迷路

前言

很早就听说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大致执行过程(先不管双向值传递):

$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的初始化

$fiber = new Fiber(function (): void {    otherFunc();});

当CPU以主线程栈区为执行堆栈执行到002行时:

在这里插入图片描述


Fiber的启动

$juice = $fiber->start();

CPU继续以主线程栈区为执行堆栈执行了005行,执行到006行时:

在这里插入图片描述


Fiber中调用otherFunc函数

CPU以Fiber栈区为执行堆栈执行003行,调用otherFunc函数,函数调用的过程参考「Chapter 4. 程序执行过程中的执行堆栈」

在这里插入图片描述


Fiber的暂停

    Fiber::suspend();

CPU以Fiber栈区为执行堆栈执行012行,执行到013行时:

在这里插入图片描述


Fiber的恢复

$fiber->resume();

CPU以主线程栈区为执行堆栈执行007行,执行到008行时(同上述 Fiber的启动过程):

在这里插入图片描述


Fiber的终结

CPU以Fiber栈区为执行堆栈执行014行,otherFuc函数返回(栈帧出栈),回到004行,Fiber终结时:

在这里插入图片描述


源码验证

以上过程只是猜测,为了验证这个猜测的正确性与正确度,带着三脚猫的C记忆来看一下Fiber的源码实现:


核心代码文件

先找到几个主要文件中的主要代码块(还好PHP源码很容易就能找到这俩文件):


Fiber核心结构

为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核心功能

为了方便阅读,源码中仅留下重要代码,前后用代替了:

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);}

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);}

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));}

阅读小结

到了这里,可以知道猜测跟实现略有出入,但基本差不多。

总结与扩展

  1. 这样看起来,Fiber(纤程)其实就是一种有栈协程(用户态线程)的实现,因此它具备全部协程的特点;
  2. Fiber本身是一种N:1的线程模型,也许可以结合多线程扩展来实现类似Golang的N:M模型(pthread被放弃了,还未尝试,不过也需要结合第5点);
  3. Fiber将全部的切换过程完全交由用户(开发者)控制,没有像Golang在runtime或者说引擎/VM中做任何掌控/协助调度的功能;
  4. Fiber本身并没有解决IO阻塞问题,如果直接用它不会提升效率,反而会带来额外的性能开销和阅读成本;
  5. 如果想要有所发挥,感觉需要在Cli运行模式下封装非阻塞IO和fiber调度器(react和amp基本都是这思路,只是基于生成器+yield方案),同时用第2点的方式构建N:M模型来避免阻塞调用的影响,但这样基本都构成一套简易Golang的调度runtime(参考「Chapter 9. Go goroutine与其调度过程」);
  6. 而在FPM运行模式下,感觉有点没什么用武之地,更像是一个底层的玩具API(或许官方在8.1加入Fiber只是第一步,未来可能会从各方面动作来配合实现性能和并发的提升,但势必也意味着复杂和变化吧~)。

最后贴一个Fiber和Swoole的瓜,许久没关注,错过了刀光剑影~

来源地址:https://blog.csdn.net/u011757697/article/details/128256823

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯