PHP 闭包和生成器可以保存循环引用

php 闭包和生成器可以保存循环引用

循环引用是 PHP 应用程序中内存泄漏的常见根源。 当对象之间直接或间接相互引用时,就会产生循环引用。虽然 PHP 的垃圾收集器能够识别并清除这些循环引用,但这会消耗 CPU 资源,并可能导致应用程序性能下降。

当内存中存在 10,000 个潜在的循环对象或数组,且其中一个超出作用域时,垃圾收集器就会被触发。

如果少量对象占用大量内存,垃圾收集器可能永远不会被触发。即使内存被孤立对象占用,也可能达到内存限制。

因此,识别并避免循环引用至关重要。 对于 Web 应用,理想情况下,应该禁用垃圾收集器,让 PHP 在发送响应后释放所有内存。 但对于长时间运行的脚本(如守护进程或工作线程),这样做存在风险,因为内存泄漏会累积,频繁的垃圾收集会降低应用程序速度。

本文将探讨闭包和生成器如何产生循环引用,以及如何避免这些问题。

立即学习“PHP免费学习笔记(深入)”;

理解循环引用典型的循环引用示例使用弱引用避免循环引用闭包与循环引用生成器与循环引用总结

理解循环引用

典型的循环引用示例

class A {    public B $b;    public function __construct() {        $this->b = new B($this);    }}class B {    public function __construct(public A $a) {}}

在这个例子中,AB 对象相互引用。创建 A 实例时,会创建一个引用 AB 实例,从而形成循环引用。

为了检测循环引用,我们可以手动触发垃圾收集器 gc_collect_cycles(),并使用 gc_status() 查看已收集的引用数量。

// 创建对象但不赋值给变量new A();gc_collect_cycles();print_r(gc_status());

输出结果类似:

array (    ...    [collected] => 2    ...)

这表明垃圾收集器检测并清除了两个存在循环引用的对象。

还可以使用 xdebug_debug_zval() 函数查看对象的引用计数。

使用弱引用避免循环引用

解决循环引用的一种方法是使用弱引用。弱引用是指不会阻止垃圾收集器回收其所引用对象的引用。在 PHP 中,可以使用 WeakReference 类创建弱引用。

需要对代码进行一些修改。B 类现在存储 WeakReference 对象而不是 A 对象。 必须使用 WeakReference 对象的 get() 方法访问 A 对象。

class A {    public B $b;    public function __construct() {        $this->b = new B($this);    }}class B {    /** @var WeakReference $a */    public WeakReference $a;    public function __construct(A $a) {        $this->a = WeakReference::create($a);    }}

再次运行垃圾收集器:

// 创建对象但不赋值给变量new A();gc_collect_cycles();print_r(gc_status());// [collected] => 0

输出结果中,收集的引用数量为 0。

提示 1: 仅在必要时使用弱引用来避免循环引用。

闭包与循环引用

PHP 中的闭包允许创建一个函数,该函数可以访问其父作用域中的变量。如果不注意,这可能会导致循环引用。

function createCircularReference() {    $a = new stdClass();    $a->b = function () use ($a) {        return $a;    };    return $a;}

在这个例子中,闭包 $a->b 引用了父作用域中的变量 $a。由于引用是显式的,所以很容易发现循环引用。

但是,如果使用闭包的简写语法(箭头函数),同样的问题可能会更隐蔽。使用箭头函数时,变量 $a 没有在闭包中显式引用,但仍然通过引用捕获。

function createCircularReference() {    $a = new stdClass();    $a->b = fn() => $a;    return $a;}createCircularReference();gc_collect_cycles();print_r(gc_status());// [collected] => 2

在这个例子中,收集的引用数量为 2,表示存在循环引用。

在闭包中引用 $this

在类方法中创建的任何非静态闭包都会持有对对象实例 ($this) 的引用,即使 $this 没有被访问。

class A {    public Closure $closure;    public function __construct() {        $this->closure = function () {};    }}new A();gc_collect_cycles();print_r(gc_status());// [collected] => 2

这是因为 $this 引用总是被闭包隐式捕获。可以使用 Reflection::getClosureThis() 来访问它。

class A {    public Closure $closure;    public function __construct() {        $this->closure = static function () {};    }}new A();gc_collect_cycles();print_r(gc_status());// [collected] => 0

如果闭包是在全局作用域或静态方法中创建的,则 $this 引用为 null。

提示 2: 如果不需要 $this,创建闭包时始终使用 static function () {}static fn () => {}

生成器与循环引用

生成器在未耗尽之前会保留引用。

在这个例子中,类将生成器存储在一个属性中,但生成器持有对对象实例 $this 的引用。生成器的行为类似于闭包,并保留对对象实例的引用。

class A {    public iterable $iterator;    public function __construct() {        $this->iterator = $this->generator();    }    private function generator(): Generator {        yield;    }}new A();gc_collect_cycles();print_r(gc_status());// [collected] => 1

类实例被垃圾收集器回收,因为它持有对生成器的引用,而生成器又持有对对象实例的引用。

一旦生成器耗尽,引用就会被释放,对象实例也会被删除。

iterator_to_array((new A())->iterator);gc_collect_cycles();print_r(gc_status());// [collected] => 0

提示 3: 确保始终迭代以耗尽生成器。

提示 4: 使用静态方法或闭包创建生成器,避免保留对对象实例的引用。

总结

循环引用是 PHP 中内存泄漏的常见原因。即使垃圾收集器可以检测和清理循环引用,它也会消耗 CPU 资源并降低应用程序速度。必须识别创建这些循环引用并调整代码以防止它们发生。使用弱引用可以避免循环引用,但一些简单的技巧可以帮助你从一开始就避免循环引用:

如果不需要 $this,创建闭包时使用 static function () {}static fn () => {}。确保始终迭代以耗尽生成器。使用静态方法或闭包创建生成器,避免保留对对象实例的引用。

更多阅读

PHP 垃圾收集 — 性能注意事项什么是 PHP 中的垃圾收集以及如何充分利用它?memprof — PHP 内存分析器,用于查找 PHP 脚本中的内存泄漏。Xdebug 的内置分析器

以上就是PHP 闭包和生成器可以保存循环引用的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1252517.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月10日 00:04:45
下一篇 2025年12月10日 00:04:53

相关推荐

  • Swiper轮播图鼠标悬停停止报错:swiper未定义如何解决?

    Swiper轮播图鼠标悬停暂停功能及常见错误解决方法 Swiper插件常用于实现图片轮播效果,其中一个常见需求是鼠标悬停时暂停自动播放,移开鼠标后继续播放。然而,不少开发者在实现此功能时遇到“swiper未定义”的错误。本文将分析此问题并提供解决方案。 问题描述: 部分用户使用Swiper 3.4.…

    2025年12月10日
    000
  • Swiper轮播图鼠标悬停停止报错:如何解决“swiper is not defined”问题?

    Swiper轮播图鼠标悬停暂停功能及“swiper未定义”错误的修复 Swiper插件常用于实现图片轮播,其中一个常见需求是鼠标悬停暂停自动播放,移开继续播放。然而,不少用户在实现此功能时遇到“swiper is not defined”错误。本文将以Swiper 3.4.2版本为例,分析并解决此问…

    2025年12月10日
    000
  • Swiper轮播图鼠标悬停停止报错:如何解决swiper is not defined?

    Swiper轮播图鼠标悬停暂停功能实现及“swiper is not defined”错误排查 在Swiper轮播图中,实现鼠标悬停暂停自动播放,离开后继续播放,是一个常见的需求。本文将针对Swiper 3.4.2版本,分析一个常见的“swiper is not defined”错误,并提供解决方案…

    2025年12月10日
    000
  • Swiper自动轮播鼠标悬停停止报错:如何解决“swiper is not defined”问题?

    Swiper轮播图鼠标悬停暂停及继续播放功能实现及“swiper is not defined”错误解决方法 许多开发者在使用Swiper插件实现鼠标悬停暂停自动轮播功能时,可能会遇到swiper is not defined错误。本文将详细分析此问题并提供解决方案。 问题描述: 在Swiper 3…

    2025年12月10日
    000
  • PHP中如何用匿名函数实现闭包并返回指定字符串?

    PHP匿名函数闭包:返回指定字符串 本文演示如何在PHP中利用匿名函数创建闭包,并使其返回特定字符串。 我们将使用一个函数来调用另一个匿名函数,后者负责返回预定义的字符串。 以下代码展示了实现方法: function myClosureExample($prefix, callable $closu…

    2025年12月10日
    000
  • PHP闭包函数如何实现字符串拼接输出?

    PHP闭包函数实现字符串拼接输出 本文演示如何利用PHP闭包函数实现“输出的结果为:123456”的输出效果。 PHP闭包函数允许在函数内部访问外部作用域变量。以下代码定义了一个名为test()的函数,它接收一个字符串和一个函数作为参数: function test($string, $functi…

    2025年12月10日
    000
  • 如何用MySQL查询一年内下单天数最多的200位用户?

    高效筛选一年内下单次数最多的200位用户 在庞大的用户订单数据库中,快速识别一年内下单天数最多的用户至关重要。本文提供一种基于MySQL的查询方案,帮助您高效提取并排名这200位用户。 SQL查询语句: SELECT COUNT(*) AS 下单天数, user_idFROM 订单表WHERE da…

    2025年12月10日
    000
  • PHP反射递归函数中,$reflect变量的值是如何变化的?

    深入理解PHP反射中的递归函数 本文分析一个PHP反射递归函数get(),探讨其在递归过程中$reflect变量值的变化情况。该函数旨在获取类的反射对象,并处理其构造函数参数。 递归过程步步分解 初始调用: 函数首次调用时,传入Person类作为参数。$reflect变量被赋值为Person类的反射…

    2025年12月10日
    000
  • TP5.1自定命令如何调用其他控制器方法?

    ThinkPHP 5.1 自定义命令调用其他控制器方法的解决方法 在ThinkPHP 5.1中,自定义命令和控制器运行在不同的环境下:命令行环境和HTTP请求环境。直接在自定义命令中调用控制器方法会因为作用域差异而失败。 问题描述:尝试在自定义命令中调用同一目录下的其他控制器方法,但执行失败。 解决…

    2025年12月10日
    000
  • PHP反射递归调用中,变量作用域如何影响反射对象的变化?

    PHP反射递归调用中的作用域与反射对象 本文探讨在PHP反射机制中使用递归时,变量作用域如何影响反射对象的变化。 问题: 在PHP反射递归调用中,反射对象似乎在递归过程中被修改。 以下代码片段展示了这个问题: 立即学习“PHP免费学习笔记(深入)”; public function get($cla…

    2025年12月10日
    000
  • PHP反射递归调用中,$reflect变量是如何保持不变的?

    PHP反射递归调用中局部变量$reflect的生存周期 本文分析PHP反射机制中递归调用时$reflect变量的行为。以下代码片段演示了这个微妙的问题: public function get($class){ if (isset($this->objectTree[$class])) { $…

    2025年12月10日
    000
  • PHP静态方法利弊权衡:到底该不该在TP框架中全面使用?

    ThinkPHP框架中全面使用静态方法的利与弊分析 在ThinkPHP框架开发中,有人建议全面采用静态方法以减少对象创建。这种做法是否可行?本文将深入探讨PHP静态方法的优缺点,并分析其在ThinkPHP框架中的适用性。 静态方法的优势: 内存效率高:静态方法无需为每个对象分配内存,降低内存消耗。性…

    2025年12月10日
    000
  • 我在php中建造了`wc’

    最近,我尝试了John Crickett的编码挑战,并决定分享我的经验。第一个挑战是使用PHP重写经典的Unix工具wc(单词计数器)。虽然我自1997年以来就一直使用Linux,但wc并非我常用的工具,因此我决定深入研究一下。 我最初的想法是用文本编辑器直接编写代码,使用Vim在SSH连接下,平板…

    2025年12月10日
    000
  • 以正确的方式解决问题:利用框架在快速修复

    我们的应用需要将CSV文件导入数据库,其中包含日期字段。为此,我们编写了一个日期解析器: class DateParser { public function parse(mixed $value): string { try { return Carbon::parse($value)->f…

    2025年12月10日
    000
  • WordPress:限制是由工具还是开发人员放置的?

    资深开发者眼中,WordPress常被误解为功能受限的平台。然而,我的经验恰恰相反。WordPress已发展成为一个强大的生态系统,足以构建复杂的商业应用。Gutenberg编辑器、完善的本地API以及与现代技术(如React)的集成,都展现了其无限潜力。 关键在于,WordPress只是我们工具箱…

    2025年12月10日
    000
  • 检查一个字符串交换是否可以使字符串相等

    判断字符串交换后是否相等 难度: 简单 主题: 哈希表,字符串,计数 给定两个长度相等的字符串 s1 和 s2。一次字符串交换操作是指选择字符串中两个索引(可以相同),并交换这两个索引上的字符。 如果可以通过恰好一次字符串交换操作使两个字符串相等,则返回 true;否则,返回 false。 示例 1…

    2025年12月10日
    000
  • 设计一个数字容器系统

    设计一个高效的数字容器系统,支持以下操作: 插入/替换: 将指定索引处的值替换为新值。如果索引不存在,则插入新值。查找最小索引: 返回给定数字在容器中出现的最小索引。如果数字不存在,则返回 -1。 挑战难度: 中等 相关主题: 哈希表,设计模式,最小堆(优先队列) 示例: [“NumberConta…

    2025年12月10日
    000
  • 扩展Laravel Optimize命令

    Laravel optimize 命令用于提升应用性能。它将配置文件、路由和视图编译成单个文件,减少运行时加载的文件数量,从而提高效率。 建议在部署到生产环境前使用此命令。 执行命令: php artisan optimize optimize 命令底层调用 IlluminateFoundation…

    2025年12月10日
    000
  • 与作曲家制作和共享PHP库

    Composer已成为PHP项目依赖管理和代码复用的核心工具。无论您是贡献开源项目还是提升个人开发效率,学习创建Composer包都是一项非常有价值的技能。本文将引导您完成构建和共享个人PHP库的完整流程。 准备工作 在开始之前,请确保您已具备以下条件: 扎实的PHP和Composer基础知识。已在…

    2025年12月10日
    000
  • 升级到PHP

    本文档记录了在Ubuntu系统上安装或升级PHP 8.2的步骤,希望能帮助到您和其他人。 首先,更新系统软件包列表: sudo dpkg -l | grep php | tee packages.txtsudo add-apt-repository ppa:ondrej/php # 按提示键入sud…

    2025年12月10日
    000

发表回复

登录后才能评论
关注微信