Home  >  Article  >  PHP Framework  >  When asked three times by the swoole coroutine, I almost cried!

When asked three times by the swoole coroutine, I almost cried!

coldplay.xixi
coldplay.xixiforward
2020-12-03 17:46:574099browse

swoole tutorialIntroduction to interview questions related to coroutinesWhen asked three times by the swoole coroutine, I almost cried!

Recommended (free): swoole tutorial

What is a process?

The process is the startup instance of the application. Independent file resources, data resources, and memory space.

What is a thread?

Threads belong to processes and are the executors of programs. A process contains at least one main thread and can also have more child threads. Threads have two scheduling strategies, one is: time-sharing scheduling, and the other is: preemptive scheduling.

My official penguin group

What is a coroutine?

Coroutines are lightweight threads, coroutines also belong to threads, and coroutines are executed in threads. The scheduling of coroutines is manually switched by the user, so it is also called user space thread. The creation, switching, suspension, and destruction of coroutines are all memory operations, and the consumption is very low. The scheduling strategy of coroutines is: collaborative scheduling.

The principle of Swoole coroutine

  • Swoole4 Since it is single-threaded and multi-process, there will only be one coroutine running in the same process at the same time.

  • Swoole server receives data and triggers the onReceive callback in the worker process to generate a Ctrip. Swoole creates a corresponding Ctrip for each request. Sub-coroutines can also be created in coroutines.

  • The underlying implementation of the coroutine is single-threaded, so there is only one coroutine working at the same time, and the execution of the coroutine is serial.

  • So when multi-tasking and multi-coroutine are executed, when one coroutine is running, other coroutines will stop working. The current coroutine will hang when performing blocking IO operations, and the underlying scheduler will enter the event loop. When an IO completion event occurs, the underlying scheduler resumes the execution of the coroutine corresponding to the event. . Therefore, coroutines do not have IO time consumption and are very suitable for high-concurrency IO scenarios. (As shown below)

When asked three times by the swoole coroutine, I almost cried!

Swoole’s coroutine execution process

  • The coroutine has no IO and is waiting for normal execution PHP code will not cause execution flow switching

  • When the coroutine encounters IO, it will wait and immediately switch control. After the IO is completed, the execution flow will be switched back to the original coroutine. Click

  • Coroutines and parallel coroutines are executed in sequence, the same logic as the previous one

  • Coroutine nested execution process is entered layer by layer from outside to inside until IO occurs, and then switches to the outer coroutine. The parent coroutine will not wait for the end of the child coroutine

The execution sequence of the coroutine

Let’s take a look at the basics first Example:

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

go() is the abbreviation of \Co::create(), used to create a coroutine, accepting callback as a parameter, in callback The code will be executed in this newly created coroutine.

Remarks: \Swoole\Coroutine can be abbreviated to \Co

The above code Execution result:

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

The execution result seems to be no different from the order in which we usually write code. Actual execution process:

  • Run this code, the system starts a new Process

  • encounters go(), a coroutine is generated in the current process, heelo go1 is output in the coroutine, and the coroutine exits

  • The process continues to execute the code, and outputs hello main

  • generates a coroutine, and outputs # in the coroutine ##heelo go2, coroutine exits

Run this code, the system starts a new process. If you don’t understand this sentence, you can use the following code:

// co.php<?phpsleep (100);
Execute and use

ps aux to view the processes in the system:

root@b98940b00a9b /v/w/c/p/swoole# php co.php &⏎
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
⏎
Let’s make a slight change and experience the scheduling of coroutines:

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

\Co::sleep() The function function is similar to sleep(), but it simulates IO waiting (IO will be discussed in detail later). The execution result is as follows:

root@b98940b00a9b /v/w/c/p/swoole# php co.phphello main
hello go2
hello go1
Why is it not executed sequentially? Actual execution process:

    Run this code, the system starts a new process
  • encounters
  • go(), A coroutine is generated in the current process
  • The coroutine encounters IO blocking (here is
  • Co::sleep() simulated IO waiting), the coroutine gives up control and enters the coroutine The process scheduling queue
  • process continues to execute downwards, outputs
  • hello main
  • executes the next coroutine, and outputs
  • hello go2
  • The previous coroutine is ready, continue execution, and output
  • hello go1
At this point, you can already see the relationship between the coroutine and the process in swoole, as well as the scheduling of the coroutine , let’s change the program just now:

go(function () {
    Co::sleep(1);
    echo "hello go1 \n";});echo "hello main \n";go(function () {
    Co::sleep(1);
    echo "hello go2 \n";});
I think you already know what the output looks like:

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理论 的实现.

The above is the detailed content of When asked three times by the swoole coroutine, I almost cried!. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:csdn.net. If there is any infringement, please contact admin@php.cn delete