Swoole4.x之協程變量訪問安全與協程鏈接池實現

訪問安全問題

爲何說有訪問安全問題呢?傳統地,在php的的環境中,不多有Phper遇到所謂變量安全訪問問題。舉個例子,代碼大約以下:php

class db
{
    protected static $instance;
    protected $dbCon;
    
    function __construct()
    {
        /*
         * 咱們這裏用stdclass來模擬一個數據庫鏈接
         */
        $this->dbCon = new \stdClass();
    }

    public static function getInstance()
    {
        if(!isset(self::$instance)){
            self::$instance = new db();
        }
        return self::$instance;
    }

    function dbCon()
    {
        return $this->dbCon;
    }
}

$con = db::getInstance()->dbCon();
$con->key = 'new';
var_dump($con->key);

這個是在fpm模式下,很常見的數據庫鏈接單例模式的使用。乍一看沒有問題,但實際上,在協程環境下,會出現鏈接跨協程使用問題,舉例以下git

go(function (){
    go(function (){
        db::getInstance()->dbCon()->key = 'one';
        //假設這sql執行了1s
        \co::sleep(1);
        var_dump(db::getInstance()->dbCon()->key);
    });
    go(function (){
        db::getInstance()->dbCon()->key = 'two';
        //假設這sql執行了0.1s
        \co::sleep(0.1);
        var_dump(db::getInstance()->dbCon()->key);
    });
});

咱們會發現,以上代碼當中,協程2的數據污染到了協程1的數據,那麼所以這樣確定是不行的。github

上下文管理器

爲了解決這個問題,咱們引入協程上下文管理這樣的概念,由此來實現每一個協程環境內的數據隔離。sql

class dbContext
{
    private $container = [];

    private static $instance;

    public static function getInstance()
    {
        if(!isset(self::$instance)){
            self::$instance = new dbContext();
        }
        return self::$instance;
    }

    function dbCon()
    {
        $cid = \co::getCid();
        if(!isset($this->container[$cid])){
            $this->container[$cid] = new stdClass();
            defer(function (){
                $this->destroy();
            });
        }
        return $this->container[$cid];
    }

    function destroy()
    {
        $cid = \co::getCid();
        if(!isset($this->container[$cid])){
            unset($this->container[$cid]);
        }
    }
}

go(function (){
    go(function (){
        dbContext::getInstance()->dbCon()->key = 'one';
        //假設這sql執行了1s
        \co::sleep(1);
        var_dump(dbContext::getInstance()->dbCon()->key);
    });
    go(function (){
        dbContext::getInstance()->dbCon()->key = 'two';
        //假設這sql執行了0.1s
        \co::sleep(0.1);
        var_dump(dbContext::getInstance()->dbCon()->key);
    });
});

以上代碼中,咱們用每一個協程的id,來做爲每一個協程棧的數據token,用了defer方法,實現了每一個協程退出的時候的數據自動清理,從而避免了內存泄露。數據庫

通用版本的鏈接池與協程上下文管理

咱們不難發現,以上代碼中,實際上依舊是短鏈接的管理方式,沒辦法對連接進行復用,因爲本文章僅作基礎原理講解之用,具體有興趣的同窗,能夠查看下Easyswoole這個框架的鏈接池和協程上下文管理器,項目主頁在 www.easyswoole.com ,若以爲喜歡,有幫助,能夠給easyswoole的github倉庫點個贊。安全

相關文章
相關標籤/搜索