Home > Article > PHP Framework > When asked three times by the swoole coroutine, I almost cried!
The process is the startup instance of the application. Independent file resources, data resources, and memory space.
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
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.
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)
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
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
// 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 go1Why is it not executed sequentially? Actual execution process:
, A coroutine is generated in the current process
simulated IO waiting), the coroutine gives up control and enters the coroutine The process scheduling queue
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 ⏎
大家可能听到使用协程的最多的理由, 可能就是 协程快. 那看起来和平时写得差不多的代码, 为什么就要快一些呢? 一个常见的理由是, 可以创建很多个协程来执行任务, 所以快. 这种说法是对的, 不过还停留在表面.
首先, 一般的计算机任务分为 2 种:
其次, 高性能相关的 2 个概念:
了解了这些, 我们再来看协程, 协程适合的是 IO 密集型 应用, 因为协程在 IO阻塞 时会自动调度, 减少IO阻塞导致的时间损失.
我们可以对比下面三段代码:
$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就绪时恢复运行
我们将多协程版稍微修改一下:
$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 ⏎
接触过 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 go
与 hello main
的顺序也和 swoole 中的协程不一样.
原因就在于 swoole 和 go 中, 实现协程调度的模型不同.
上面 go 代码的执行过程:
package main
, 然后执行其中的 func mian()
hello main
time.Sleep(time.Second)
, main 函数执行完, 程序结束, 进程退出, 导致调度中的协程也终止go 中的协程, 使用的 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!