# 協程
協程說復雜不復雜說難也不難,一句話可以概括:能提高并發,但不能加速任務,同步代碼實現異步IO,異步非阻塞的代碼塊。
協程是一種特殊函數,是一種可以掛起的函數,然后可以從掛起的地方重新恢復執行,一個線程內的多個協程是串行的,跟CPU處理進程一樣,同一時刻只能一個協程在線程上運行,除非出讓了控制權給別的協程運行。協程無法利用多核CPU因此協程只能解決并發問題,不能解決任務處理速度問題。協程就是把一個大任務再分成更小的片段,封裝程一個函數,當其中一個協程需要IO阻塞的時候,主動掛起當前協程,把控制權交給其他協程運行。
我們知道進程和線程是由操作系統調度的,什么時候執行取決于操作系統什么時候把CPU時間交給某個進程或者線程,而協程是什么時候交出控制權是由用戶決定的。進程和線程屬于內核態,協程屬于用戶態線程。
協程是一種用戶態的輕量級線程,協程的調度完全由用戶控制。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文的切換非常快。
### 協程特點
* 1、用戶態線程、遇到IO主動讓出控制權
* 2、多個協程代碼依然是串行的,無需加鎖
* 3、開銷低,只占用內存,不存在進程、線程切換開銷
* 4、并發量大,單個進程可開啟50w個協程
* 5、隨時隨地,只要想并發,就調用go創建協程

<center>圖:1.5-1</center>
我們知道線程是輕量級的進程,那么協程就是輕量級的線程。協程運行在線程之上,一個線程可以有多個協程。
我們知道在進程遇到阻塞的時候開多一個線程在進程內部切換,避免每次都切換進程,這樣可以更大力度的使用CPU分給這個進程的可使用時間。而協程跟線程和進程的關系很類似,只不過協程是跟線程直接建立關系。

<center>圖:1.5-2</center>
圖:1.5-2 是多個線程之間切換的示意圖,那么我們來考慮一下,如果線程只是等待IO操作(網絡或者文件),那么為什么像線程重復使用進程一樣來重復的使用這個線程呢?我們把IO去掉,看看這個圖是什么樣子的。

<center>圖:1.5-3</center>
去掉IO部分操作,可以看出來基本上這個并發請求應用程序代碼可以在 `單個線程中` 運行,協程最大力度的利用了線程等待IO的時間,讓程序在等待IO的時候可以執行別的業務代碼。

<center>圖:1.5-4</center>
從圖:1.5-4 看著像不像一個線程的執行流程,這就是協程的魅力所在,當一個協程被yield之后會被掛起,把控制權轉移給線程內部的其他協程,因為是在線程上進行的切換,所以開銷遠遠比進程和線程低很多。

<center>圖:1.5-5</center>
當程序調用協程之后,當前協程會主動讓出控制權交給同一個線程內的其他協程處理,類似圖:1.5-5所示,開發者代碼中需要使用IO的時候主動讓出協程的控制權給別的協程使用。

<center>圖:1.5-6</center>
去掉IO部分再看協程的處理,就跟圖:1.5-4所示的一樣,直接執行的都是業務邏輯,避免遇到IO導致線程轉換到等待狀態,更充分的利用CPU分給這個線程的執行時間。
> 注意:協程并不能讓任務加速進行,只能執行更多任務。
協程由于是建立在線程之上的,因此沒有辦法使用CPU多核心的優勢,協程適合適用于IO密集運算的場景。
### 協程有什么作用?
協程是為了提高CPU使用率,避免在線程阻塞的時候大量的線程上下文切換。
```php
echo "1-start\n";
sleep(1);
echo "1-end\n";
echo "2-start\n";
sleep(1);
echo "2-end\n";
echo "3-start\n";
sleep(1);
echo "3-end\n";
echo "4-start\n";
sleep(1);
echo "4-end\n";
```

<center>圖:1.5-7</center>
以上代碼的CPU使用率僅有`1%`
```php
Swoole\Runtime::enableCoroutine(true);
go(function () {
echo "go1-start\n";
sleep(1);
echo "go1-end\n";
});
go(function () {
echo "go2-start\n";
sleep(1);
echo "go2-end\n";
});
go(function () {
echo "go3-start\n";
sleep(1);
echo "go3-end\n";
});
go(function () {
echo "go4-start\n";
sleep(1);
echo "go4-end\n";
});
```

<center>圖:1.5-8</center>
使用協程,成功把CPU使用率提高到了`4%`,這樣CPU就不需要為了IO阻塞而空跑,或者進行上下文切換。之前不是說過協程不能加速嗎?這里使用協程之后怎么`1秒`多就執行完了,跟前面的代碼不一樣?這里得到的時間取決于最后一個協程執行結束的時間。
### 協程的執行順序
```php
Swoole\Runtime::enableCoroutine(true);
go(function(){
sleep(2);
echo "go1\n";
});
go(function(){
sleep(1);
echo "go2\n";
});
echo "main\n";
```
先輸出:main->go2->go1
### 協程之間通訊
多個協程之間通訊,采用Channel實現,多個協程協助完成共同的任務。
```php
Swoole\Runtime::enableCoroutine(true);
$chan = new Swoole\Coroutine\Channel();
go(function () use ($chan){
sleep(1);
$chan->push(['name'=>'sunny']);
});
go(function() use ($chan){
$data = $chan->pop();
print_r($data);
});
echo "結束\n";
```
- 第一章:基礎知識
- 課程簡介
- PHP-FPM過渡常駐內存
- 進程
- 實戰:實現Master-Worker
- 線程
- 實戰:CC攻擊器
- 協程
- 實戰:實現waitGroup功能
- 進程、線程、協程的區別
- 第二章:初識Swoft2.0
- Swoft介紹
- Swoft環境安裝
- gcc升級
- 安裝Swoft框架
- 目錄結構介紹
- SwoftCli工具
- Swoft配置
- 第三章:Swoft2.0核心
- 上下文
- 常駐內存沒有上下文隔離
- 實戰:手寫swoole框架上下文管理
- Bean容器
- 實戰:根據容器原理實現容器
- 實戰:通過容器實現依賴注入
- Bean容器定義與使用
- 配置文件定義Bean
- 容器類型
- 面向接口的容器
- 注解
- 實戰:實現注解
- 自定義Swoft注解類
- 事件
- 連接池
- 實戰:Swoole實現連接池
- 第四章:Http服務器
- Http Server生命周期
- Http Server配置
- 控制器
- 路由
- 請求對象Request
- 響應對象Response
- Http異常處理
- 中間件
- 實戰:中間件實現JWT登陸授權
- 第五章:驗證器
- 內置驗證類型
- 驗證器的使用
- 自定義驗證器
- 第六章:數據庫操作
- 連接數據庫
- 實體模型
- 模型事件
- 查詢器
- 事務處理
- 連接池配置
- 讀寫分離
- 多數據庫切換
- Models分層結構
- 實戰:實現用戶CURD API
- 第七章:Redis
- 連接redis和使用
- Redis連接池
- Redis集群配置(單機版)
- Redis集群配置(多服務器)
- Redis連接集群
- Redis實戰:實現延時任務
- 第八章:AOP編程
- AOP概念
- AOP實現原理
- 實戰實現AOP:靜態代理
- 實戰實現AOP:動態代理
- 切面注解介紹
- PointExecution切面
- PointBean切面
- PointAnnotation切面
- 實戰:使用AOP實現日志記錄
- 第九章:任務處理
- 進程使用
- 進程池使用
- 實戰:進程消費隊列
- 實戰:進程實現RabbitMQ延時隊列
- 異步任務
- 協程任務
- 定時任務