• EasySwoole的ContextManager的分析和使用


    EasySwoole的ContextManager的分析和使用

    ContextManager主要用来实现协程上下文的隔离,框架中实现隔离的原理简单粗暴,easyswoole使用了进程粒度的单例ContextManager将不同协程下的变量,以各协程为粒度,存储在各自协程id下,最终形式就是二维数组,直观感觉就是将各协程的上下文隔离开来了。

    easyswoole的上下文管理器还实现了类似懒加载和协程退出的hook机制,核心代码就是curd,大致如下。

    简单讲解

    简单应用

    分析

    <?php
    
    namespace EasySwooleComponentContext;
    
    use EasySwooleComponentContextExceptionModifyError;
    use EasySwooleComponentSingleton;
    use SwooleCoroutine;
    
    class ContextManager
    {
        use Singleton;
    
        private $contextHandler = [];
    
        private $context = [];
    
        private $deferList = [];
    
        // 为指定key注册handler,在对应时机hook
        public function registerItemHandler($key, ContextItemHandlerInterface $handler): ContextManager
        {
            $this->contextHandler[$key] = $handler;
            return $this;
        }
    
        // 向manager中注册
        public function set($key, $value, $cid = null): ContextManager
        {
            if (isset($this->contextHandler[$key])) {
                throw new ModifyError('key is already been register for context item handler');
            }
            // 获取协程id
            $cid = $this->getCid($cid);
            // es的contextmanager 在写方法中也提供了cid参数,也就说我们可以跨协程修改其他协程的上下文了
            // 这种方式灵活性可能更高 相对的提高了开发负担
            $this->context[$cid][$key] = $value;
            return $this;
        }
    
        public function get($key, $cid = null)
        {
            $cid = $this->getCid($cid);
            if (isset($this->context[$cid][$key])) {
                return $this->context[$cid][$key];
            }
            if (isset($this->contextHandler[$key])) {
                /** @var ContextItemHandlerInterface $handler */
                // 调用注册的handeler的onContextCreate方法
                // 实现类似懒加载机制
                $handler = $this->contextHandler[$key];
                $this->context[$cid][$key] = $handler->onContextCreate();
                return $this->context[$cid][$key];
            }
            return null;
        }
    
        public function unset($key, $cid = null)
        {   
            // 删除写操作同样提供了cid参数
            $cid = $this->getCid($cid);
            if (isset($this->context[$cid][$key])) {
                if (isset($this->contextHandler[$key])) {
                    /** @var ContextItemHandlerInterface $handler */
                    // 执行注册的onDestroy方法
                    $handler = $this->contextHandler[$key];
                    $item = $this->context[$cid][$key];
                    unset($this->context[$cid][$key]);
                    return $handler->onDestroy($item);
                }
                unset($this->context[$cid][$key]);
                return true;
            } else {
                return false;
            }
        }
    
        public function destroy($cid = null)
        {
            $cid = $this->getCid($cid);
            if (isset($this->context[$cid])) {
                $data = $this->context[$cid];
                foreach ($data as $key => $val) {
                    $this->unset($key, $cid);
                }
            }
            unset($this->context[$cid]);
        }
    	
        // 值得注意的是每个方法都调用了getCid方法 也就是说执行了manager的任何方法都会注册defer进行,
        // 从而执行清理工作
        public function getCid($cid = null): int
        {
            if ($cid === null) {
                // 如果没指定cid 那么获取当前协程id
                $cid = Coroutine::getUid();
                // 如果deferList中不存在对应的cid 并且cid合法
                // 那么就注册defer 在协程退出时清除指定的协程上下文
                if (!isset($this->deferList[$cid]) && $cid > 0) {
                    $this->deferList[$cid] = true;
                    Coroutine::defer(function () use ($cid) {
                        unset($this->deferList[$cid]);
                        $this->destroy($cid);
                    });
                }
                return $cid;
            }
            return $cid;
        }
    
        public function destroyAll($force = false)
        {
            if ($force) {
                $this->context = [];
            } else {
                foreach ($this->context as $cid => $data) {
                    $this->destroy($cid);
                }
            }
        }
    
        public function getContextArray($cid = null): ?array
        {
            $cid = $this->getCid($cid);
            if (isset($this->context[$cid])) {
                return $this->context[$cid];
            } else {
                return null;
            }
        }
    }
    
    # 不知道作者出于什么考量,所有写操作同样允许传入cid,有的竞品框架此部分功能并不支持类似功能
    # 至于为什么提供了onDestroy功能,es官方给的说法是可以执行类似回收资源的操作。
    # 但这种操作这无非是将回收的执行逻辑放到了不同位置的defer中,类似回收资源的逻辑完全可以在取出连接处处理,或者连接池直接支持回收功能。
    

    使用

    public function ctxSetGet()
    {
        $pcid = Coroutine::getCid();
        ContextManager::getInstance()->set('parent', 'parent');
        go(function () use ($pcid) {
            $parentValue = ContextManager::getInstance()->get('parent', $pcid);
            var_dump($parentValue); // parent
            ContextManager::getInstance()->set('parent', 'modified by sub', $pcid);
        });
        Coroutine::sleep(0.1);
        # 可以看到跨协程修改了数据
        $v = ContextManager::getInstance()->get('parent');
        echo $v, PHP_EOL; // modified by sub
    }
    
    # 此案例抄自es官网
    class Handler implements ContextItemHandlerInterface
    {
    
        function onContextCreate()
        {
            $class = new stdClass();
            $class->time = time();
            return $class;
        }
    
        function onDestroy($context)
        {
            var_dump($context);
        }
    }
    
    ContextManager::getInstance()->registerItemHandler('key',new Handler());
    
    go(function (){
        go(function (){
            ContextManager::getInstance()->get('key');
        });
        co::sleep(1);
        ContextManager::getInstance()->get('key');
    });
    

    实际上,swoole在4.3后提供了原生context api 返回值为ArrayObject类型,方便各位一顿操作,协程退出后上下文自动清理,具体细节可自行查看文档。easyswoole历史原因并没有使用swoole原生context实现上下文管理。这里是我写的包,非常简单,胆子大的可生产使用。

    发现错误,欢迎指正,感谢!!!

  • 相关阅读:
    蓝桥杯 算法训练 ALGO-57 删除多余括号
    蓝桥杯 算法训练 ALGO-50 数组查找及替换
    蓝桥杯 算法训练 ALGO-60 矩阵乘法
    求最大公约数和最小公倍数的几种方法
    南阳OJ 1170 最大的数
    蓝桥杯 基础练习 BASIC-30 阶乘计算
    蓝桥杯 算法训练 ALGO-108 最大的体积
    蓝桥杯 算法训练 ALGO-114 黑白无常
    蓝桥杯 算法训练 ALGO-93 反置数
    蓝桥杯 算法训练 ALGO-21 装箱问题
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/13969731.html
Copyright © 2020-2023  润新知