swoole 協程介紹


協程的執行順序:

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

echo "hello main \n";

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

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

備注:\Swoole\Coroutine 可以簡寫為 \Co

上面的代碼執行結果:

# php co.php
hello go1
hello main
hello go2

實際執行過程:

  • 運行此段代碼,系統啟動一個新進程
  • 遇到 go() ,當前進程中生成一個協程,協程中輸出 hello go1,協程退出
  • 進程繼續向下執行代碼,輸出 hello main 
  • 再生成一個協程,協程中輸出 hello go2,協程退出

下面稍微改一下執行順序

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等待,執行的順序如下:

# php co.php
hello main
hello go2
hello go1

上面的實際執行過程如下:

  • 運行此段代碼,系統啟動一個進程
  • 遇到 go(),當前進程中生成一個協程
  • 協程中遇到IO阻塞(這里是 Co::sleep() 模擬出來的IO等待),協程讓出控制,進入協程調度隊列
  • 進程繼續向下執行,輸出 hello main
  • 執行下一個協程,輸出 hello go2
  • 之前的協程准備就緒,繼續執行,輸出 hello go1

協程快在哪?減少IO阻塞導致的性能損失

一般的計算機任務分為兩種:

  • CPU密集型,比如加減乘除等科學計算
  • IO密集型,比如網絡請求,文件讀寫等

高性能相關的兩個概念:

  • 並行:同一個時刻,同一個CPU只能執行同一個任務,要同時執行多個任務,就需要有多個CPU才行
  • 並發:由於CPU切換任務非常快,所以讓人感覺像是有多個任務同時執行

協程適合的場景是IO密集型應用,因為協程在IO阻塞時會自動調度,減少IO阻塞導致的時間損失。

普通版:執行4個任務

$n = 4;
for ($i = 0; $i < $n; $i++) {
    sleep(1);
    echo microtime(true) . ": hello $i \n";
};
echo "hello main \n";

執行結果:

# php co.php
1528965075.4608: hello 0
1528965076.461: hello 1
1528965077.4613: hello 2
1528965078.4616: hello 3
hello main
real    0m 4.02s
user    0m 0.01s
sys     0m 0.00s

單個協程版:

$n = 4;
go(function () use ($n) {
    for ($i = 0; $i < $n; $i++) {
        Co::sleep(1);
        echo microtime(true) . ": hello $i \n";
    };
});
echo "hello main \n";

執行結果:

# php co.php
hello main
1528965150.4834: hello 0
1528965151.4846: hello 1
1528965152.4859: hello 2
1528965153.4872: hello 3
real    0m 4.03s
user    0m 0.00s
sys     0m 0.02s

多協程版本:

$n = 4;
for ($i = 0; $i < $n; $i++) {
    go(function () use ($i) {
        Co::sleep(1);
        echo microtime(true) . ": hello $i \n";
    });
};
echo "hello main \n";

執行結果:

# php co.php
hello main
1528965245.5491: hello 0
1528965245.5498: hello 3
1528965245.5502: hello 2
1528965245.5506: hello 1
real    0m 1.02s
user    0m 0.01s
sys     0m 0.00s

這三種版本為什么時間上有很大的差異?

  • 普通版本:會遇到IO阻塞,導致的性能損失
  • 單協程版本:盡管IO阻塞引發了協程調度,但當前只有一個協程,調度之后還是執行當前協程
  • 多協程版本:真正發揮出協程的優勢,遇到IO阻塞時發生調度,IO就緒時恢復運行

下面將多協程版本修改為CPU密集型

$n = 4;
for ($i = 0; $i < $n; $i++) {
    go(function () use ($i) {
        // Co::sleep(1);
        sleep(1);
        echo microtime(true) . ": hello $i \n";
    });
};
echo "hello main \n";

執行的結果:

# php co.php
1528965743.4327: hello 0
1528965744.4331: hello 1
1528965745.4337: hello 2
1528965746.4342: hello 3
hello 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 < $cnt; $i++) {
    $redis = new \Redis();
    $redis->connect('redis');
    $redis->auth('123');
    $key = $redis->get('key');
}

// 單協程版: 只有一個協程, 並沒有使用到協程調度減少 IO 阻塞
go(function () use ($cnt) {
    for ($i = 0; $i < $cnt; $i++) {
        $redis = new Co\Redis();
        $redis->connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    }
});

// 多協程版, 真正使用到協程調度帶來的 IO 阻塞時的調度
for ($i = 0; $i < $cnt; $i++) {
    go(function () {
        $redis = new Co\Redis();
        $redis->connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    });
}

性能對比:

# 多協程版
# php co.php
real    0m 0.54s
user    0m 0.04s
sys     0m 0.23s

# 同步版
# php co.php
real    0m 1.48s
user    0m 0.17s
sys     0m 0.57s

 

swoole協程和go協程對比:單進程 VS 多線程

package main

import (
    "fmt"
    "time"
)

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

    fmt.Println("hello main")

    time.Sleep(time.Second)
}

執行結果:

$ go run test.go
hello main
hello go

go代碼的執行過程如下:

  • 運行 go 代碼,系統啟動一個新進程
  • 查找 package main ,然后執行其中的 func main()
  • 遇到協程,交給協程調度器執行
  • 繼續向下執行,輸出 hello main 
  • 如果不添加 time.Sleep(time.Second),main函數執行完,程序結束,進程退出,導致調度中的協程也終止

swoole和go實現協程調度的模型不同,go中使用的是MPG模型:

  • M 指的是 Machine, 一個M直接關聯了一個內核線程
  • P 指的是 processor, 代表了M所需的上下文環境, 也是處理用戶級代碼邏輯的處理器
  • G 指的是 Goroutine, 其實本質上也是一種輕量級的線程

 

而swoole中的協程調度使用單進程模型,所有協程都是在當前進程中進行調度,單進程的好處是:簡單 / 不用加鎖 / 性能高。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM