swoole 協程介紹


 

協程的執行順序:

1
2
3
4
5
6
7
8
9
go( function  () {
     echo  "hello go1 \n" ;
});
 
echo  "hello main \n" ;
 
go( function  () {
     echo  "hello go2 \n" ;
});

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

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

上面的代碼執行結果:

1
2
3
4
# php co.php
hello go1
hello main
hello go2

實際執行過程:

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

下面稍微改一下執行順序

1
2
3
4
5
6
7
8
9
10
11
12
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等待,執行的順序如下:

1
2
3
4
# 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個任務

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

執行結果:

1
2
3
4
5
6
7
8
9
# 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

單個協程版:

1
2
3
4
5
6
7
8
$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" ;

執行結果:

1
2
3
4
5
6
7
8
9
# 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

多協程版本:

1
2
3
4
5
6
7
8
$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" ;

執行結果:

1
2
3
4
5
6
7
8
9
# 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密集型

1
2
3
4
5
6
7
8
9
$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" ;

執行的結果:

1
2
3
4
5
6
7
8
9
# 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 同步版, 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' );
     });
}

性能對比:

1
2
3
4
5
6
7
8
9
10
11
# 多協程版
# 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 多線程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package  main
 
import  (
     "fmt"
     "time"
)
 
func  main() {
     go  func () {
         fmt.Println( "hello go" )
     }()
 
     fmt.Println( "hello main" )
 
     time.Sleep(time.Second)
}

執行結果:

1
2
3
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中的協程調度使用單進程模型,所有協程都是在當前進程中進行調度,單進程的好處是:簡單 / 不用加鎖 / 性能高。

 https://www.cnblogs.com/xi-jie/articles/10466610.html


免責聲明!

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



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