PHP interview met the interviewer's swoole coroutine for three consecutive questions, almost crying!

What is a process?

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

What is a thread?

The thread belongs to the process and is the executor of the program. A process contains at least one main thread, and can have more child threads. There are two scheduling strategies for threads, one is: time-sharing scheduling, and the other is: preemptive scheduling.

My official penguin colony

What is a coroutine?

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

The principle of Swoole coroutine

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

  • The 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. A sub-coroutine can also be created in the coroutine.

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

  • Therefore, 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 there is an IO completion event, the underlying scheduler resumes the execution of the coroutine corresponding to the event. . Therefore, there is no time-consuming IO for coroutines, which is very suitable for high concurrent IO scenarios. (As shown below)

Insert picture description here

Swoole's coroutine execution process

  • The coroutine has no IO and waits for the normal execution of the PHP code, and no execution flow switching occurs

  • When the coroutine encounters IO and waits to immediately cut the control right, after the IO is completed, switch the execution flow back to the point where the coroutine cut out

  • Parallel coroutines are executed in sequence, same as the previous logic

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

The execution order of the coroutine

Let's take a look at the basic example:

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

echo "hello main \n";

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

go()It is the \Co::create()acronym used to create a coroutine, accepts as a parameter callback, the callback code executes in this new coroutine.

Remarks: \Swoole\Coroutinecan be abbreviated as\Co

The execution result of the above code:

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

The execution result and the order in which we usually write the code seem to be the same. The actual execution process:

  • Run this code, the system starts a new process

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

  • The process continues to execute the code, output hello main

  • Generate another coroutine, output heelo go2in the coroutine, and exit the coroutine

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

// co.php
<?php

sleep(100);

The implementation and use ps auxProcess Viewer system:

root@b98940b00a9b /v/w/c/p/swoole# php co.php &
⏎
root@b98940b00a9b /v/w/c/p/swoole# ps aux
PID   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 change it a bit and experience the scheduling of the coroutine:

use Co;

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

echo "hello main \n";

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

\Co::sleep()Features and functions sleep()similar, but it simulates the IO wait (IO will go into detail later) implementation of the results are as follows:

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

Why is it not executed sequentially? The actual execution process:

  • Run this code, the system starts a new process
  • Encountered go(), a coroutine is generated in the current process
  • Coroutine encountered IO blocking (here is Co::sleep()to simulate the IO wait), coroutines give up control, scheduling queue to enter the coroutine
  • The process continues to execute downward, output hello main
  • Execute the next coroutine, output hello go2
  • The previous coroutine is ready, continue execution, 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 previous program:

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.php
hello main
hello go1
hello go2
⏎

Where is the coroutine fast? Reduce performance loss caused by IO blocking

You may hear the most common reason for using coroutines, perhaps because coroutines are fast. Why should code that seem to be written similarly in normal times be faster? A common reason is that many coroutines can be created to execute Task, so fast. This statement is correct, but it still stays on the surface.

First of all, general computer tasks are divided into two types:

  • CPU-intensive, such as scientific calculations such as addition, subtraction, multiplication and division
  • IO intensive, such as network requests, file reading and writing, etc.

Secondly, there are 2 concepts related to high performance:

  • Parallel: At the same time, the same CPU can only execute the same task. To execute multiple tasks at the same time, multiple CPUs are required.
  • Concurrency: Since the CPU switches tasks very quickly, reaching the limit that humans can perceive, there will be the illusion of many tasks being executed at the same time

Knowing this, let's look at the coroutine again.The coroutine is suitable for IO-intensive applications, because the coroutine is automatically scheduled when IO is blocked, reducing the time loss caused by IO blocking.

We can compare the following three pieces of code:

  • Normal version: Perform 4 tasks
$n = 4;
for ($i = 0; $i < $n; $i++) {
    
    
    sleep(1);
    echo microtime(true) . ": hello $i \n";
};
echo "hello main \n";
root@b98940b00a9b /v/w/c/p/swoole# time 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
⏎
  • Single coroutine version:
$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";
root@b98940b00a9b /v/w/c/p/swoole# time 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
⏎
  • Multi-coroutine version: witness the moment of miracle
$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";
root@b98940b00a9b /v/w/c/p/swoole# time 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
⏎

Why is there such a big difference in time:

  • Ordinary writing, will encounter performance loss caused by IO blocking

  • Single coroutine: Although IO blocking triggered coroutine scheduling, there is currently only one coroutine, and the current coroutine is still executed after scheduling

  • Multi-coroutine: Really exerts the advantages of coroutine, scheduling occurs when IO is blocked, and resumes operation when IO is ready

We will slightly modify the multi-coroutine version:

  • Multi-coroutine version 2: CPU intensive
$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";
root@b98940b00a9b /v/w/c/p/swoole# time 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
⏎

Just Co::sleep()changed sleep(), and time has almost the same as the ordinary version:

  • sleep() It can be regarded as a CPU-intensive task, which will not cause the scheduling of the coroutine

  • Co::sleep()The simulation is IO-intensive tasks, which will trigger the scheduling of coroutines.
    This is why, coroutines are suitable for IO-intensive applications.

Here is another set of comparative examples: using 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');
    });
}

Performance comparison:

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

# 同步版
root@0124f915c976 /v/w/c/p/swoole# time php co.php
real    0m 1.48s
user    0m 0.17s
sys     0m 0.57s
⏎

Comparison of swoole coroutine and go coroutine: single process vs multi thread

I have been in contact with the coder of the go coroutine, and the initial contact with the swoole coroutine will be a bit confused, for example, compare the following code:

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

Just go write coroutine coder, when writing this code will be told not to forget time.Sleep(time.Second), or do not see the output hello go, and secondly, hello gowith hello mainthe order and also in swoole coroutine not the same.

The reason is that swoole and go have different models for implementing coroutine scheduling.

The execution process of the above go code:

  • Run go code, the system starts a new process
  • Find package main, and then executefunc mian()
  • When encountering a coroutine, give it to the coroutine scheduler to execute
  • Continue to execute downward, output hello main
  • If you don't add it time.Sleep(time.Second), the main function is executed, the program ends, and the process exits, causing the coroutine in scheduling to also terminate

The coroutine in go, the MPG model used:

  • M refers to Machine, a M is directly associated with a kernel thread
  • P refers to the processor, which represents the context required by M, and is also a processor that processes user-level code logic
  • G refers to Goroutine, which is essentially a lightweight thread

MPG model

The goroutine scheduling in swoole uses a single-process model, all goroutines are scheduled in the current process, and the benefits of a single process are also obvious-simple / no locks / high performance.

Whether it is go's MPG model or swoole's single-process model, it is an implementation of CSP theory.

The CSP communication method was already available in a paper in 1985. For those who do theoretical research, it may be difficult to improve without the bold assumptions that can be made several years, ten or even decades in advance.

Pay attention, don't get lost

Alright, everyone, the above is the entire content of this article. The people who can see here are all talents . As I said before, there are a lot of technical points in PHP, because there are too many, it is really impossible to write, and you will not read too much after writing it, so I will organize it into PDF and documents here, if necessary Can

Click to enter the secret code: PHP+「Platform」

Insert picture description here

Insert picture description here


For more learning content, please visit the [Comparative Standard Factory] excellent PHP architect tutorial catalog, as long as you can read it to ensure that the salary will rise a step (continuous update)

The above content hopes to help everyone . Many PHPers always encounter some problems and bottlenecks when they are advanced. There is no sense of direction when writing too much business code. I don’t know where to start to improve. I have compiled some information about this, including But not limited to: distributed architecture, high scalability, high performance, high concurrency, server performance tuning, TP6, laravel, YII2, Redis, Swoole, Swoft, Kafka, Mysql optimization, shell scripts, Docker, microservices, Nginx, etc. Many knowledge points, advanced advanced dry goods, can be shared with everyone for free, and those who need can join my PHP technology exchange group

Guess you like

Origin blog.csdn.net/weixin_49163826/article/details/108870894