協(xié)程編程注意事項(xiàng)
1.協(xié)程內(nèi)部禁止使用全局變量,以免發(fā)生數(shù)據(jù)錯(cuò)亂;(非多協(xié)程協(xié)作場(chǎng)景)
原因:協(xié)程是共享進(jìn)程資源的,也就是全局變量共享,用來(lái)處理任務(wù)時(shí),全局變量很容易被別的協(xié)程篡改,導(dǎo)致數(shù)據(jù)錯(cuò)亂。
2.協(xié)程使用?use?關(guān)鍵字引入外部變量到當(dāng)前作用域禁止使用引用,以免發(fā)生數(shù)據(jù)錯(cuò)亂;
(非多協(xié)程協(xié)作場(chǎng)景)
原因:引用是原變量的真實(shí)地址,由于協(xié)程是共享進(jìn)程資源的,會(huì)導(dǎo)致原變量很容易被別的協(xié)程篡改,導(dǎo)致數(shù)據(jù)錯(cuò)亂。
3.不能使用 ?(非多協(xié)程協(xié)作場(chǎng)景)
(1)類靜態(tài)變量?Class::$array?
(2)全局變量?$_array?
(3)全局對(duì)象屬性?$object->array
(4)其他超全局變量$GLOBALS ? 等保存協(xié)程上下文內(nèi)容,以免發(fā)生數(shù)據(jù)錯(cuò)亂;
上下文Context類實(shí)際上采用標(biāo)記協(xié)程id的方式來(lái)分發(fā)存儲(chǔ)各個(gè)協(xié)程對(duì)應(yīng)的數(shù)據(jù)資源(數(shù)據(jù)池):
use Swoole\Coroutine;class Context{protected static $pool = []; //進(jìn)程創(chuàng)建后此靜態(tài)變量就會(huì)存在,但只會(huì)根據(jù)對(duì)應(yīng)的id去覆蓋對(duì)應(yīng)協(xié)程下的數(shù)據(jù)// 基于協(xié)程 `ID` 獲取數(shù)據(jù)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;}// 基于協(xié)程 `ID` 寫入數(shù)據(jù)static function put($key, $item){$cid = Coroutine::getCid();if ($cid > 0){self::$pool[$cid][$key] = $item;}}// 基于協(xié)程 `ID` 刪除數(shù)據(jù)static function delete($key = null){$cid = Coroutine::getCid();if ($cid > 0){if($key){unset(self::$pool[$cid][$key]);}else{unset(self::$pool[$cid]);}}}}
4.協(xié)程之間通訊必須使用通道(Channel)場(chǎng)景:如果需要使用多協(xié)程協(xié)作執(zhí)行任務(wù)時(shí)
Coroutine\Channel?使用本地內(nèi)存,不同的進(jìn)程之間內(nèi)存是隔離的。
只能在同一進(jìn)程的不同協(xié)程內(nèi)進(jìn)行?push?和?pop?操作。
不過(guò)理論上仍然有共享內(nèi)存的方式,只是需要進(jìn)行上鎖,保持同步機(jī)制
5.不能在多個(gè)協(xié)程間共用一個(gè)客戶端連接,以免發(fā)生數(shù)據(jù)錯(cuò)亂;可以使用連接池實(shí)現(xiàn);
原因:同樣是因?yàn)檫B接標(biāo)識(shí)共享,有可能前腳一個(gè)協(xié)程剛對(duì)鏈接做了操作,后腳被別的協(xié)程改了數(shù)據(jù)。(非多協(xié)程協(xié)作場(chǎng)景)
$pool = new RedisPool();$server = new Swoole\Http\Server('127.0.0.1', 9501);$server->set([// 如開(kāi)啟異步安全重啟, 需要在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) {//從連接池中獲取一個(gè)Redis協(xié)程客戶端$redis = $pool->get();//連接失敗if ($redis === false) {$resp->end("ERROR");return;}$result = $redis->hgetall('key');$resp->end(var_export($result, true));//釋放客戶端,其他協(xié)程可復(fù)用此對(duì)象$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(){//有空閑連接且連接池處于可用狀態(tài)if ($this->available && count($this->pool) > 0) {return $this->pool->pop();}//無(wú)空閑連接,創(chuàng)建新連接$redis = new Swoole\Coroutine\Redis();$res = $redis->connect('127.0.0.1', 6379);if ($res == false) {return false;} else {return $redis;}}public function destruct(){// 連接池銷毀, 置不可用狀態(tài), 防止新的客戶端進(jìn)入常駐連接池, 導(dǎo)致服務(wù)器無(wú)法平滑退出$this->available = false;while (!$this->pool->isEmpty()) {$this->pool->pop();}}}
原因:使得客戶端鏈接在整個(gè)進(jìn)程周期中可用。
7.在 Swoole\Process 中,客戶端連接應(yīng)當(dāng)在 Swoole\Process->start 后,子進(jìn)程的回調(diào)函數(shù)中創(chuàng)建;
原因:使得客戶端鏈接在整個(gè)子進(jìn)程周期中可用。
8.必須在協(xié)程內(nèi)捕獲異常,不得跨協(xié)程捕獲異常;
原因:多協(xié)程下,try/catch和throw在不同的協(xié)程中,協(xié)程內(nèi)無(wú)法捕獲到此異常。當(dāng)協(xié)程退出時(shí),發(fā)現(xiàn)有未捕獲的異常,將引起致命錯(cuò)誤。
錯(cuò)誤:try {Swoole\Coroutine::create(function () {throw new \RuntimeException(__FILE__, __LINE__);});}catch (\Throwable $e) {echo $e;}#try/catch和throw在不同的協(xié)程中,協(xié)程內(nèi)無(wú)法捕獲到此異常。當(dāng)協(xié)程退出時(shí),發(fā)現(xiàn)有未捕獲的異常,將引起致命錯(cuò)誤。正解:function test() {throw new \RuntimeException(__FILE__, __LINE__);}Swoole\Coroutine::create(function () {try {test();}catch (\Throwable $e) {echo $e;}});
9.在__get /__set魔術(shù)方法中不能有協(xié)程切換。(跟php本身有關(guān))
