天天看点

hyperf 协程数据混淆的思考和分析

<?php
namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;

/**
 * @AutoController()
 */

 class CoController{

    private $foo = 1;
    
    public function get(){
        return $this->foo;
    }
    public function update(RequestInterface $request){
        $foo = $request -> input('foo');
        $this->foo = $foo;
        return $this->foo;
    }
 }
           

get 返回的是1 这一步是对的

hyperf 协程数据混淆的思考和分析

第二步 更新值返回更新值是2 也是对的

hyperf 协程数据混淆的思考和分析

第三步 设想是返回1 但是返回的是2

单例对不同请求的影响 怎么处理这种情况往下看

hyperf 协程数据混淆的思考和分析

处理过程

处理方法一:协程上下文

由于同一个进程内协程间是内存共享的,但协程的执行/切换是非顺序的,也就意味着我们很难掌控当前的协程是哪一个*(事实上可以,但通常没人这么干)*,所以我们需要在发生协程切换时能够同时切换对应的上下文。

在 Hyperf 里实现协程的上下文管理将非常简单,基于 Hyperf\Utils\Context 类的 set(string $id, $value)、get(string $id, $default = null)、has(string $id)、override(string $id, \Closure $closure) 静态方法即可完成上下文数据的管理,通过这些方法设置和获取的值,都仅限于当前的协程,在协程结束时,对应的上下文也会自动跟随释放掉,无需手动管理,无需担忧内存泄漏的风险。

<?php
namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\Context;

/**
 * @AutoController()
 */

 class CoController{

    public function get(){
        //从当前协程上下文中取出 key 为 foo 的值,如不存在则返回 null 字符串
        return Context::get('foo', 'null');
    }
    public function update(RequestInterface $request){
        $foo = $request -> input('foo');
        Context::set('foo', $foo);
        return Context::get('foo');
    }
 }
           
第一步请求get方法  返回为空
           
hyperf 协程数据混淆的思考和分析

第二步更新值 更新返回值为100

hyperf 协程数据混淆的思考和分析

第三步 在获取看看是不是有影响–没有影响

hyperf 协程数据混淆的思考和分析

处理方法二:协程上下文 + 魔术方法

<?php
namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\Context;

/**
 * @AutoController()
 */

 class CoController{

    public function get(){
        return $this->foo;
    }
    public function update(RequestInterface $request){
        $foo = $request -> input('foo');
        $this->foo = $foo;
        return $this->foo;
    }

    public function __get($name){
    	//这个加class怕别的类有一样的, 这样确保一个类里面就一个
        return Context::get(__CLASS__ . ":" . $name, 'null');
    }

    public function __set($name, $value){
        return Context::set(__CLASS__. ":" . $name, $value);
    }
 }
           

//返回都正常,没有影响

hyperf 协程数据混淆的思考和分析

通过Inject引入的类 – 出现的问题是会影响数据

<?php
namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\Context;
use Hyperf\Di\Annotation\Inject;

/**
 * @AutoController()
 */
 class CoController{
    /**
     * @Inject()
     * @var \App\Foo
     */
    private $foo;
    public function get(){
        return $this->foo->bar;
    }
    public function update(RequestInterface $request){
        $foo = $request -> input('foo');
        $this->foo->bar = $foo;
        return $this->foo->bar;
    }
 }
           
<?php
namespace App;

Class Foo{

    public $bar = 1;
}
           

//影响了数据—无论是不是静态属性,都会在全局共享,所以要把所有的值都存储到协程上下文中,协程上下文会在协程结束的时候释放掉,无需担忧内存泄漏的问题

hyperf 协程数据混淆的思考和分析

那么问题, 如果是这种引入类的方法应该怎么解决呢

//引入这的代码不需要改变

<?php
namespace App\Controller;

use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\Context;
use Hyperf\Di\Annotation\Inject;

/**
 * @AutoController()
 */
 class CoController{
    /**
     * @Inject()
     * @var \App\Foo
     */
    private $foo;
    public function get(){
        return $this->foo->bar;
    }
    public function update(RequestInterface $request){
        $foo = $request -> input('foo');
        $this->foo->bar = $foo;
        return $this->foo->bar;
    }


 }
           

//foo类改变就可以

<?php
namespace App;

use Hyperf\Utils\Context;
Class Foo{

    public function __set($name, $value){
        return Context::set(__CLASS__ . ":" .$name, $value);
    }

    public function __get($name){
        return Context::get(__CLASS__ . ":" . $name);
    }
}
           

效果

hyperf 协程数据混淆的思考和分析