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.
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)
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\Coroutine
can 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, outputheelo go1
in the coroutine, and the coroutine exits -
The process continues to execute the code, output
hello main
-
Generate another coroutine, output
heelo go2
in 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 aux
Process 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 go
with hello main
the 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
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」
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