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

訪問安全問題

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

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模式下,很常見的數據庫連接單例模式的使用。乍一看沒有問題,但實際上,在協程環境下,會出現連接跨協程使用問題,舉例如下

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的數據,那麼因此這樣肯定是不行的。

上下文管理器

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

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倉庫點個贊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章