訪問安全問題
爲什麼說有訪問安全問題呢?傳統地,在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倉庫點個贊。