首頁  >  文章  >  php框架  >  當被swoole協程三連問時,快哭了!

當被swoole協程三連問時,快哭了!

coldplay.xixi
coldplay.xixi轉載
2020-12-03 17:46:574114瀏覽

swoole教學介紹相關協程的面試問題當被swoole協程三連問時,快哭了!

推薦(免費):swoole教學

什麼是進程?

流程就是應用程式的啟動實例。獨立的檔案資源,資料資源,記憶體空間。

什麼是執行緒?

執行緒屬於進程,是程式的執行者。一個行程至少包含一個主線程,也可以有更多的子執行緒。執行緒有兩種調度策略,一是:分時調度,二是:搶佔式調度。

我的官方企鵝群

什麼是協程?

協程是輕量級線程,協程也是屬於線程,協程是在線程裡執行的。協程的調度是使用者手動切換的,所以又叫用戶空間線程。協程的創建、切換、掛起、銷毀全部為記憶體操作,消耗量是非常低的。協程的調度策略是:協作式調度。

Swoole 協程的原理

  • Swoole4 由於是單執行緒多進程的,同一時間同一個行程只會有一個協程在執行。

  • Swoole server 接收資料在 worker 程序觸發 onReceive 回調,產生一個攜程。 Swoole 為每個請求建立對應攜程。協程中也能創造子協程。

  • 協程在底層實作上是單執行緒的,因此同一時間只有一個協程在工作,協程的執行是串列的。

  • 因此多任務多協程執行時,一個協程正在執行時,其他協程會停止工作。當前協程執行阻塞 IO 操作時會掛起,底層調度器會進入事件循環。當有 IO 完成事件時,底層調度器恢復事件對應的協程的執行。 。所以協程不存在 IO 耗時,非常適合高併發 IO 場景。 (如下圖)

當被swoole協程三連問時,快哭了!

Swoole 的協程執行流程

  • ##協程沒有IO 等待正常執行PHP 程式碼,不會產生執行流程切換

  • 協程遇到IO 等待立即將控制權切,待IO 完成後,重新將執行流切回原來協程切出的點

  • 協程並行協程依序執行,同上一個邏輯

  • 協程嵌套執行流程由外向內逐層進入,直到發生IO,然後切到外層協程,父協程不會等待子協程結束

#協程的執行順序

先來看看基礎的範例:

go(function () {
    echo "hello go1 \n";});echo "hello main \n";go(function () {
    echo "hello go2 \n";});

go()\Co::create() 的縮寫, 用來建立一個協程, 接受callback 作為參數, callback 中的程式碼, 會在這個新建的協程中執行.

備註:

\Swoole\Coroutine 可以簡寫為\Co##上面的程式碼執行結果:

root@b98940b00a9b /v/w/c/p/swoole# php co.phphello go1
hello main
hello go2

執行結果和我們平時寫程式碼的順序, 好像沒啥區別.實際執行過程:

    運行此段程式碼, 系統啟動一個新行程
  • 遇到
  • go()

    , 在目前行程中產生一個協程, 協程中輸出heelo go1, 協程退出

  • 程式繼續向下執行程式碼, 輸出
  • hello main

  • #再產生一個協程, 協程中輸出
  • heelo go2

    , 協程退出

  • 運行此段程式碼, 系統啟動一個新進程. 如果不理解這句話, 你可以使用如下程式碼:
// co.php<?phpsleep (100);

執行並使用

ps aux

查看系統中的進程:<pre class="brush:php;toolbar:false">root@b98940b00a9b /v/w/c/p/swoole# php co.php &amp;⏎ root@b98940b00a9b /v/w/c/p/swoole# ps auxPID   USER     TIME   COMMAND     1 root       0:00 php -a   10 root       0:00 sh   19 root       0:01 fish  749 root       0:00 php co.php  760 root       0:00 ps aux ⏎</pre>我們來稍微改一改, 體驗協程的調度:

use Co;go(function () {
    Co::sleep(1); // 只新增了一行代码
    echo "hello go1 \n";});echo "hello main \n";go(function () {
    echo "hello go2 \n";});

#\Co::sleep()

函數函數和sleep() 差不多, 但是它模擬的是IO等待(IO後面會細講).執行的結果如下:<pre class="brush:php;toolbar:false">root@b98940b00a9b /v/w/c/p/swoole# php co.phphello main hello go2 hello go1</pre>怎麼不是順序執行的呢?實際執行程序:

運行此段程式碼, 系統啟動一個新程序
  • 遇到
  • go()
  • ,在當前進程中產生一個協程協程中遇到IO阻塞(這裡是
  • Co::sleep()
  • 模擬出的IO等待), 協程讓出控制, 進入協程調度佇列進程繼續向下執行, 輸出
  • hello main
  • 執行下一個協程, 輸出
  • hello go2
  • #之前的協程準備就緒, 繼續執行, 輸出
  • hello go1
  • 到這裡, 已經可以看到swoole 中協程與進程的關係, 以及協程的調度, 我們再改一改剛才的程式:
go(function () {
    Co::sleep(1);
    echo "hello go1 \n";});echo "hello main \n";go(function () {
    Co::sleep(1);
    echo "hello go2 \n";});

我想你已經知道輸出是什麼樣子了:

root@b98940b00a9b /v/w/c/p/swoole# php co.phphello main
hello go1
hello go2
⏎

协程快在哪? 减少IO阻塞导致的性能损失

大家可能听到使用协程的最多的理由, 可能就是 协程快. 那看起来和平时写得差不多的代码, 为什么就要快一些呢? 一个常见的理由是, 可以创建很多个协程来执行任务, 所以快. 这种说法是对的, 不过还停留在表面.

首先, 一般的计算机任务分为 2 种:

  • CPU密集型, 比如加减乘除等科学计算
  • IO 密集型, 比如网络请求, 文件读写等

其次, 高性能相关的 2 个概念:

  • 并行: 同一个时刻, 同一个 CPU 只能执行同一个任务, 要同时执行多个任务, 就需要有多个 CPU 才行
  • 并发: 由于 CPU 切换任务非常快, 快到人类可以感知的极限, 就会有很多任务 同时执行 的错觉

了解了这些, 我们再来看协程, 协程适合的是 IO 密集型 应用, 因为协程在 IO阻塞 时会自动调度, 减少IO阻塞导致的时间损失.

我们可以对比下面三段代码:

  • 普通版: 执行 4 个任务
$n = 4;for ($i = 0; $i <pre class="brush:php;toolbar:false">root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965075.4608: hello 01528965076.461: hello 11528965077.4613: hello 21528965078.4616: hello 3hello main
real    0m 4.02s
user    0m 0.01s
sys     0m 0.00s
⏎
  • 单个协程版:
$n = 4;go(function () use ($n) {
    for ($i = 0; $i <pre class="brush:php;toolbar:false">root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965150.4834: hello 01528965151.4846: hello 11528965152.4859: hello 21528965153.4872: hello 3real    0m 4.03s
user    0m 0.00s
sys     0m 0.02s
⏎
  • 多协程版: 见证奇迹的时刻
$n = 4;for ($i = 0; $i <pre class="brush:php;toolbar:false">root@b98940b00a9b /v/w/c/p/swoole# time php co.phphello main1528965245.5491: hello 01528965245.5498: hello 31528965245.5502: hello 21528965245.5506: hello 1real    0m 1.02s
user    0m 0.01s
sys     0m 0.00s
⏎

为什么时间有这么大的差异呢:

  • 普通写法, 会遇到 IO阻塞 导致的性能损失

  • 单协程: 尽管 IO阻塞 引发了协程调度, 但当前只有一个协程, 调度之后还是执行当前协程

  • 多协程: 真正发挥出了协程的优势, 遇到 IO阻塞 时发生调度, IO就绪时恢复运行

我们将多协程版稍微修改一下:

  • 多协程版2: CPU密集型
$n = 4;for ($i = 0; $i <pre class="brush:php;toolbar:false">root@b98940b00a9b /v/w/c/p/swoole# time php co.php1528965743.4327: hello 01528965744.4331: hello 11528965745.4337: hello 21528965746.4342: hello 3hello main
real    0m 4.02s
user    0m 0.01s
sys     0m 0.00s
⏎

只是将 Co::sleep() 改成了 sleep(), 时间又和普通版差不多了. 因为:

  • sleep() 可以看做是 CPU密集型任务, 不会引起协程的调度

  • Co::sleep() 模拟的是 IO密集型任务, 会引发协程的调度
    这也是为什么, 协程适合 IO密集型 的应用.

再来一组对比的例子: 使用 redis

// 同步版, redis使用时会有 IO 阻塞$cnt = 2000;for ($i = 0; $i connect('redis');
    $redis->auth('123');
    $key = $redis->get('key');}// 单协程版: 只有一个协程, 并没有使用到协程调度减少 IO 阻塞go(function () use ($cnt) {
    for ($i = 0; $i connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    }});// 多协程版, 真正使用到协程调度带来的 IO 阻塞时的调度for ($i = 0; $i connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    });}

性能对比:

# 多协程版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal    0m 0.54s
user    0m 0.04s
sys     0m 0.23s
⏎# 同步版root@0124f915c976 /v/w/c/p/swoole# time php co.phpreal    0m 1.48s
user    0m 0.17s
sys     0m 0.57s
⏎

swoole 协程和 go 协程对比: 单进程 vs 多线程

接触过 go 协程的 coder, 初始接触 swoole 的协程会有点 懵, 比如对比下面的代码:

package main

import (
    "fmt"
    "time")func main() {
    go func() {
        fmt.Println("hello go")
    }()

    fmt.Println("hello main")

    time.Sleep(time.Second)}
> 14:11 src $ go run test.go
hello main
hello go

刚写 go 协程的 coder, 在写这个代码的时候会被告知不要忘了 time.Sleep(time.Second), 否则看不到输出 hello go, 其次, hello gohello main 的顺序也和 swoole 中的协程不一样.

原因就在于 swoole 和 go 中, 实现协程调度的模型不同.

上面 go 代码的执行过程:

  • 运行 go 代码, 系统启动一个新进程
  • 查找 package main, 然后执行其中的 func mian()
  • 遇到协程, 交给协程调度器执行
  • 继续向下执行, 输出 hello main
  • 如果不添加 time.Sleep(time.Second), main 函数执行完, 程序结束, 进程退出, 导致调度中的协程也终止

go 中的协程, 使用的 MPG 模型:

  • M 指的是 Machine, 一个M直接关联了一个内核线程
  • P 指的是 processor, 代表了M所需的上下文环境, 也是处理用户级代码逻辑的处理器
  • G 指的是 Goroutine, 其实本质上也是一种轻量级的线程

MPG 模型

而 swoole 中的协程调度使用 单进程模型, 所有协程都是在当前进程中进行调度, 单进程的好处也很明显 – 简单 / 不用加锁 / 性能也高.

无论是 go 的 MPG模型, 还是 swoole 的 单进程模型, 都是对 CSP理论 的实现.

以上是當被swoole協程三連問時,快哭了!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除