協程編程注意事項
1.協程內部禁止使用全局變量,以免發生數據錯亂;(非多協程協作場景)
原因:協程是共享進程資源的,也就是全局變量共享,用來處理任務時,全局變量很容易被別的協程篡改,導致數據錯亂。
2.協程使用?use?關鍵字引入外部變量到當前作用域禁止使用引用,以免發生數據錯亂;
(非多協程協作場景)
原因:引用是原變量的真實地址,由于協程是共享進程資源的,會導致原變量很容易被別的協程篡改,導致數據錯亂。
3.不能使用 ?(非多協程協作場景)
(1)類靜態變量?Class::$array
(2)全局變量?$_array
(3)全局對象屬性?$object->array
(4)其他超全局變量$GLOBALS ? 等保存協程上下文內容,以免發生數據錯亂;
上下文Context類實際上采用標記協程id的方式來分發存儲各個協程對應的數據資源(數據池):
use Swoole\Coroutine;
class Context
{
protected static $pool = []; //進程創建后此靜態變量就會存在,但只會根據對應的id去覆蓋對應協程下的數據
// 基于協程 `ID` 獲取數據
static function get($key)
{
$cid = Coroutine::getCid();
if ($cid < 0)
{
return null;
}
if(isset(self::$pool[$cid][$key])){
return self::$pool[$cid][$key];
}
return null;
}
// 基于協程 `ID` 寫入數據
static function put($key, $item)
{
$cid = Coroutine::getCid();
if ($cid > 0)
{
self::$pool[$cid][$key] = $item;
}
}
// 基于協程 `ID` 刪除數據
static function delete($key = null)
{
$cid = Coroutine::getCid();
if ($cid > 0)
{
if($key){
unset(self::$pool[$cid][$key]);
}else{
unset(self::$pool[$cid]);
}
}
}
}
4.協程之間通訊必須使用通道(Channel)場景:如果需要使用多協程協作執行任務時
Coroutine\Channel?使用本地內存,不同的進程之間內存是隔離的。
只能在同一進程的不同協程內進行?push?和?pop?操作。
不過理論上仍然有共享內存的方式,只是需要進行上鎖,保持同步機制
5.不能在多個協程間共用一個客戶端連接,以免發生數據錯亂;可以使用連接池實現;
原因:同樣是因為連接標識共享,有可能前腳一個協程剛對鏈接做了操作,后腳被別的協程改了數據。(非多協程協作場景)
$pool = new RedisPool();
$server = new Swoole\Http\Server('127.0.0.1', 9501);
$server->set([
// 如開啟異步安全重啟, 需要在workerExit釋放連接池資源
'reload_async' => true
]);
$server->on('start', function (swoole_http_server $server) {
var_dump($server->master_pid);
});
$server->on('workerExit', function (swoole_http_server $server) use ($pool) {
$pool->destruct();
});
$server->on('request', function (swoole_http_request $req, swoole_http_response $resp) use ($pool) {
//從連接池中獲取一個Redis協程客戶端
$redis = $pool->get();
//連接失敗
if ($redis === false) {
$resp->end("ERROR");
return;
}
$result = $redis->hgetall('key');
$resp->end(var_export($result, true));
//釋放客戶端,其他協程可復用此對象
$pool->put($redis);
});
$server->start();
class RedisPool
{
protected $available = true;
protected $pool;
public function __construct()
{
$this->pool = new SplQueue;
}
public function put($redis)
{
$this->pool->push($redis);
}
/**
* @return bool|mixed|\Swoole\Coroutine\Redis
*/
public function get()
{
//有空閑連接且連接池處于可用狀態
if ($this->available && count($this->pool) > 0) {
return $this->pool->pop();
}
//無空閑連接,創建新連接
$redis = new Swoole\Coroutine\Redis();
$res = $redis->connect('127.0.0.1', 6379);
if ($res == false) {
return false;
} else {
return $redis;
}
}
public function destruct()
{
// 連接池銷毀, 置不可用狀態, 防止新的客戶端進入常駐連接池, 導致服務器無法平滑退出
$this->available = false;
while (!$this->pool->isEmpty()) {
$this->pool->pop();
}
}
}
6.在 Swoole\Server 中,客戶端連接應當在 onWorkerStart 中創建;
原因:使得客戶端鏈接在整個進程周期中可用。
7.在 Swoole\Process 中,客戶端連接應當在 Swoole\Process->start 后,子進程的回調函數中創建;
原因:使得客戶端鏈接在整個子進程周期中可用。
8.必須在協程內捕獲異常,不得跨協程捕獲異常;
原因:多協程下,try/catch和throw在不同的協程中,協程內無法捕獲到此異常。當協程退出時,發現有未捕獲的異常,將引起致命錯誤。
錯誤:
try {
Swoole\Coroutine::create(function () {
throw new \RuntimeException(__FILE__, __LINE__);
});
}
catch (\Throwable $e) {
echo $e;
}
#try/catch和throw在不同的協程中,
協程內無法捕獲到此異常。
當協程退出時,發現有未捕獲的異常,將引起致命錯誤。
正解:
function test() {
throw new \RuntimeException(__FILE__, __LINE__);
}
Swoole\Coroutine::create(function () {
try {
test();
}
catch (\Throwable $e) {
echo $e;
}
});
9.在__get /__set魔術方法中不能有協程切換。(跟php本身有關)
任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。