您好,登錄后才能下訂單哦!
這篇文章給大家介紹如何通過swoole協程實現并發編程,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
目前的Swoole 內置了豐富的協程組件供開發者直接調用以便快速實現異步非阻塞的并發編程,省去了開發者自己實現相應底層代碼的麻煩:
TCP/UDP Client:Swoole\Coroutine\Client TCP/UDP Server:Swoole\Coroutine\Server HTTP/WebSocket Client:Swoole\Coroutine\HTTP\Client HTTP/WebSocket Server:Swoole\Coroutine\HTTP\Server HTTP2 Client:Swoole\Coroutine\HTTP2\Client Redis Client:Swoole\Coroutine\Redis Mysql Client:Swoole\Coroutine\MySQL PostgreSQL Client:Swoole\Coroutine\PostgreSQL
在協程 Server 中使用對應的協程版 Client 來實現全異步 Server,同時 Swoole 提供了協程工具集:Swoole\Coroutine,提供了獲取當前協程ID、反射調用等能力。
我們以 Redis 和 MySQL 客戶端請求為例,使用上述 Swoole\Coroutine\Redis
和 Swoole\Coroutine\MySQL
組件,可以實現異步 Redis 和 MySQL 客戶端:
<?php$server = new \Swoole\Http\Server('127.0.0.1', 9588);$server->on('Request', function ($request, $response) {var_dump(time());$mysql = new Swoole\Coroutine\MySQL();$mysql->connect(['host' => '127.0.0.1','user' => 'root','password' => 'root','database' => 'laravel58',]);$mysql->setDefer();$mysql->query('select sleep(3)');var_dump(time());$redis1 = new Swoole\Coroutine\Redis();$redis1->connect('127.0.0.1', 6379);$redis1->setDefer();$redis1->set('hello', 'world');var_dump(time());$redis2 = new Swoole\Coroutine\Redis();$redis2->connect('127.0.0.1', 6379);$redis2->setDefer();$redis2->get('hello');$result1 = $mysql->recv();$result2 = $redis2->recv();var_dump($result1, $result2, time());$response->end('Request Finish: ' . time());});$server->start();
由于 Swoole 會在 TCP Server 和 HTTP Server 回調函數中會自動開啟協程,所以不需要顯式通過 go 關鍵字啟動協程,然后我們可以在回調函數中使用 MySQL 和 Redis 客戶端協程組件發起請求。
要理解上述代碼的運行原理,需要先了解協程的 setDefer 機制,絕大部分協程組件都支持 setDefer,該機制可以將請求響應式的接口拆分為兩個步驟:先發送數據, 再并發收取響應結果。
由于大多數情況下,「建立連接和發送數據的耗時」相較于「等待響應的耗時」來說可以忽略不計, 所以可以簡單理解為 defer 模式下, 多個客戶端的請求響應是并發的(實際上只有接收響應是并發的,建立連接和發送請求是串行的)。
以上述代碼為例,設置 setDefer(true)
后,通過 Redis 或 MySQL 客戶端發起請求,將不再等待服務器返回結果,而是在發送請求之后,立即返回 true
。在此之后可以繼續發起其他 Redis、MySQL 請求,最后再使用 recv()
方法接收響應內容。
我們將上述代碼保存到 coroutine/http.php
,然后在終端啟動這個 HTTP 服務端:
php coroutine/http.php
接下來,在 Postman 中對服務端發起請求,會在等待幾秒后看到返回的響應內容:
前三個時間分別是 mysql
、redis1
、redis2
三個客戶端發起請求的時間,可以看到,盡管 mysql
中會休眠 3 秒,但是通過 defer 機制實現了三個請求的并發執行。
除了 setDefer 機制外,Swoole 還支持通過子協程+通道實現并發編程,下面我們通過子協程+通道的方式來改寫上面的代碼實現:
<?php$server = new \Swoole\Http\Server('127.0.0.1', 9588);$server->on('Request', function ($request, $response) {$channel = new \Swoole\Coroutine\Channel(3);go(function () use ($channel) {var_dump(time());$mysql = new Swoole\Coroutine\MySQL();$mysql->connect(['host' => '127.0.0.1','user' => 'root','password' => 'root','database' => 'laravel58',]);$result = $mysql->query('select sleep(3)');$channel->push($result);});go(function () use ($channel) {var_dump(time());$redis1 = new Swoole\Coroutine\Redis();$redis1->connect('127.0.0.1', 6379);$result = $redis1->set('hello', 'world');$channel->push($result);});go(function () use ($channel) {var_dump(time());$redis2 = new Swoole\Coroutine\Redis();$redis2->connect('127.0.0.1', 6379);$result = $redis2->get('hello');$channel->push($result);});$results = [];for ($i = 0; $i < 3; $i++) {$results[] = $channel->pop();}$response->end(json_encode(['data' => $results,'time' => time()]));});$server->start();
我們將 MySQL 和 Redis 客戶端連接請求調用改寫為通過三個子協程實現,同時去掉 setDefer 設置,因為這三個子協程已經是并發調用了,此外,由于三個子協程之間數據是相互隔離的,所以我們通過 Swoole\Coroutine\Channel (即通道)實現協程之間的數據共享和通信,初始化其緩沖空間為 3,然后通過 use 方式將其引入到子協程中,把響應結果通過 push
方法放到 Channel 里面,接下來在服務端 onRequest 回調函數末尾通過一個循環將 Channel 中的數據通過 pop
方法依次取出來放到數組 $results
中,最后通過 $response->end()
方法將結果以 JSON 格式返回給客戶端。
我們將上述代碼保存到 coroutine/http2.php
,然后在終端通過如下命令啟動這個新的 HTTP 服務端:
php coroutine/http2.php
還是在 Postman 中請求這個服務端,將響應格式調整為 JSON,會看到結果如下:
由于 MySQL 請求執行耗時最長,所以位置最靠后。在啟動服務器的終端,可以看到打印出的三個客戶端請求時間,完全一致,說明它們是并發執行的:
顯然,通過子協程 + 通道還可以很方便的實現 Redis、MySQL 連接池。
關于如何通過swoole協程實現并發編程就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。