一、io阻塞問題
由於線程在操作io時需要從硬盤中讀寫文件時會阻塞住也就是停止運行等待io操作完成後纔會繼續往下執行程序。好了問題來了,程序線程就在那乾等着io操作完成。會導致程序效率非常低。爲什麼會阻塞呢?硬盤操作的速度要比cpu慢很多。
二、協程解決io阻塞問題
那能不能在做io操作時,不要阻塞住,而是繼續往下執行。等io操作完成後,再執行io操作完成後的代碼。
這種遇到耗時操作時掛起(yield)耗時操作,線程繼續往下執行程序,耗時操作完成後恢復(resume),恢復就是執行耗時操作完成後的回調程序。就種線程程序執行方式就是是協程。也解決了io阻塞等待問題。
但是也不是說協程越多性能越好,單個硬盤的讀寫速度還是有限的,你叫單個硬盤做一堆事,硬盤也處理不過來,所有使用協程的最好方式還是網絡調用另一臺機器來做io操作。
一般是用在水平分庫的查詢上。使用協程去遠程連接水平分庫後的數據庫服務器進行查詢。
什麼是水平分庫呢?
1、概念:以字段爲依據,按照一定策略(hash、range等),將一個庫中的數據拆分到多個庫中。
2、結果:
每個庫的結構都一樣
每個庫中的數據不一樣,沒有交集
所有庫的數據並集是全量數據
3、場景:系統絕對併發量上來了,分表難以根本上解決問題,並且還沒有明顯的業務歸屬來垂直分庫的情況下。
4、分析:庫多了,io和cpu的壓力自然可以成倍緩解
三、回調與協程的區別
回調:
通常回調是以向一個方法傳遞另一個函數作爲參數, 在該方法中, 於適當的時候調用傳遞的函數參數的形式呈現的, 回調的本質還是函數調用, 他與普通的函數調用的不同點在於, 普通的函數調用在編程時已經確定要調用哪個函數, 而回調機制是在運行時確定需要調用的函數的, 根據傳遞的作爲參數的函數不同, 其行爲也會不同
協程:
協程是指在一個函數運行到出現某種情況時暫停執行轉而執行另一個函數, 這聽起來似乎跟函數調用相同, 但協程並不是函數調用, 他的重點是執行過程中的暫停執行, 這與多線程執行時的線程切換相似, 但協程是單線執行的, 他沒有多線程切換時的開銷, 這也就使得協程在運行效率更高的同時沒有了多線程中的共享資源問題, 不再需要鎖來保證安全, 但是相對的協程也沒有了多線程對多處理器資源的利用率優勢, 不過這個缺點可以利用多進程方式來彌補
四、安裝swoole實現 協程
安裝
git clone 下載源碼地址
根據文檔,執行編譯命令,修改php.ini
協程實驗
<?php
Co\run(function () {
go(function(){
co::sleep(1);//模擬耗時操作 wait 1s 直實場景一般是mysql 或者 redis
echo 1;//耗時1秒後執行
});
go(function(){//執行
echo 2;
});
go(function(){//執行
echo 3;
});
echo "hello\n";//程序開始
});
?>
依次輸輸出2 3 hello 1 耗時操作的協程最後執行
我們熟悉的文件讀寫、網絡通訊請求(MySQL、Redis、Http等)都是屬於 I/O 密集型場景。
假設一次 SQL 查詢爲 100ms,在傳統同步模式下,當前線程在這 100ms 的時間裏,是不能做其它操作的。如果要執行十次這個 SQL,可能需要耗費 1s 以上。
而如果用協程,雖然不同協程之間也是按順序執行,但是在前一個等待 100ms 期間,底層會調度 CPU,去執行其它協程的操作。也就是說,可能第一個查詢還沒返回結果,其它幾個查詢就已經發送給了 MySQL 並正在執行中了。如果開啓十個協程,分別執行這個 SQL,可能只需要耗費 100+ms 即可完成。
那我們怎合併所有協程返回的結果呢?
可以使用swoole的watigroup
<?php
Co\run(function () {
$wg = new \Swoole\Coroutine\WaitGroup();
$result = [];
$wg->add();//協程數量加1
$time = microtime(true);
//啓動第一個協程
go(function () use ($wg, &$result) {//use 關鍵字 函數閉包 調用外部變量
co::sleep(1);
$result['data0'] = "lala";
echo "協程1 任務完成\n";
$wg->done();//本協程任務完成
});
$wg->add();//協程數量加1
//啓動第二個協程
go(function () use ($wg, &$result) {
co::sleep(2);
$result['data1'] = "lala";
echo "協程2 任務完成\n";
$wg->done();//本協程任務完成
});
//掛起父協程,等待所有子協程任務完成後恢復
$wg->wait();
echo "所有協程任務完成,總耗時".(microtime(true)-$time)."s\n";
//這裏 $result 包含了 2 個任務執行結果
var_dump($result);
});