You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Closure绑定$this导致GC无法触发__destruct的策略计数器问题

解决PHP Strategy类中Closure绑定导致析构函数不执行的问题

问题根源

核心问题是绑定后的Closure与Strategy实例形成了循环引用:

  • 构造函数中将Closure绑定到$this,导致Closure内部持有Strategy实例的强引用
  • Strategy实例又通过$this->strategy持有该Closure的引用
    这种循环引用会让PHP垃圾回收器无法及时回收对象,__destruct()自然不会触发,计数器也就无法递减。

解决方案

方法1:使用WeakReference打破循环引用(PHP 7.4+推荐)

利用PHP 7.4引入的WeakReference类,让Closure持有实例的弱引用而非强引用,这样当实例被unset后,垃圾回收器可以正常回收对象,析构函数也能执行。

修改Strategy类的构造函数和析构函数:

class Strategy
{
    private static ?StrategyCounter $strategyCounter = null;
    private string $name;
    private Closure $strategy;

    public function __construct(Closure $strategy)
    {
        self::initCounter();
        // 创建当前实例的弱引用
        $weakRef = WeakReference::create($this);

        // 重新包装闭包,通过弱引用访问实例
        $this->strategy = function (...$args) use ($strategy, $weakRef) {
            $instance = $weakRef->get();
            if (!$instance) {
                throw new RuntimeException("Strategy实例已被销毁");
            }
            // 将原闭包绑定到实例并执行
            return $strategy->bindTo($instance)(...$args);
        };

        $this->name = 'Strategy ' . self::$strategyCounter->getNextStrategyNumber();
    }

    private static function initCounter()
    {
        if (!self::$strategyCounter) {
            self::$strategyCounter = new StrategyCounter();
        }
    }

    public function __destruct()
    {
        self::$strategyCounter->decrementStrategyCounter();
    }

    // 假设你有getName方法
    public function getName(): string
    {
        return $this->name;
    }
}

方法2:手动解除引用(兼容PHP低版本)

如果你的PHP版本低于7.4,无法使用WeakReference,可以添加一个手动销毁方法,主动解除Closure的引用并递减计数器:

class Strategy
{
    // ... 其他代码不变

    public function destroy(): void
    {
        // 解除闭包引用
        unset($this->strategy);
        self::$strategyCounter->decrementStrategyCounter();
    }
}

使用时需要主动调用destroy再unset:

$a = new Strategy(function () {}); // Strategy 1
$b = new Strategy(function () {}); // Strategy 2
$b->destroy(); // 手动解除引用并递减计数器
unset($b);
$c = new Strategy(function () {}); // Strategy 2

验证效果

使用方法1的实现后,你的示例代码会按预期运行:

$a = new Strategy(function () {}); // 名称为Strategy 1
$b = new Strategy(function () {}); // 名称为Strategy 2
unset($b); // 对象被回收,__destruct执行,计数器递减
$c = new Strategy(function () {}); // 名称为Strategy 2

内容的提问来源于stack exchange,提问作者LordF

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:24:50