---5.2并发与并行(Concurrency vs Parallelism)标题①:协程是并发(一个核轮流干),多进程才是并行(多核同时干)大白话:并发一个人快速切换做多件事(看起来同时);并行多个人真的同时做。协程救不了 CPU 密集型,多进程才行。?php// concurrency_vs_parallelism.phpuse Swoole\Coroutine;use Swoole\Coroutine\WaitGroup;use Swoole\Process;use function Swoole\Coroutine\run;functioncpuWork():int{// 一份纯 CPU 活:数质数$c0;for($n2;$n200000;$n){$ptrue;for($i2;$i*$i$n;$i)if($n%$i0){$pfalse;break;}if($p)$c;}return$c;}// 场景A:协程并发做 4 份 CPU 活 ——没加速,4 份还在抢同 1 个核run(function(){$smicrotime(true);$wgnewWaitGroup();for($i0;$i4;$i){$wg-add();Coroutine::create(function()use($wg){cpuWork();$wg-done();});}$wg-wait();printf(【协程并发】4份CPU活 耗时 %.2fs(没加速,共用1核)\n,microtime(true)-$s);});// 场景B:多进程并行做 4 份 ——4 个核同时跑,约快 4 倍$smicrotime(true);for($i0;$i4;$i){(newProcess(fn()cpuWork()))-start();}for($i0;$i4;$i)Process::wait();printf(【多进程并行】4份CPU活 耗时 %.2fs(真·并行,约快4倍)\n,microtime(true)-$s);看什么:协程版4份活 ≈串行耗时(没加速),多进程版 ≈缩短到1/4。记住:I/O 密集用协程,CPU 密集用多进程。---5.2非阻塞 I/O(Non-Blocking I/O)标题②:协程让阻塞写法自动变非阻塞 大白话:你照样写 sleep、查库这种等的代码,Swoole 在底层偷偷帮你让出 CPU 去干别的。?php// nonblocking.phpuse Swoole\Coroutine;use function Swoole\Coroutine\run;run(function(){Coroutine::create(function(){echo协程1:开始等 2 秒 I/O\n;Coroutine::sleep(2);// 不会卡住整个进程echo协程1:完成\n;});Coroutine::create(function(){for($i1;$i3;$i){Coroutine::sleep(0.5);echo协程2:干活 $i(协程1还在等)\n;}});echo主协程:我没被协程1的2秒卡住\n;});看什么:协程1在等 2 秒的时候,协程2已经干了好几轮活——等待时间被利用起来了。 标题③:一键Hook ——连原生 sleep/curl/mysqli 都变非阻塞 大白话:enableCoroutine()一开,老代码不用改,所有阻塞函数自动协程化。?php// hook.phpuse Swoole\Runtime;use Swoole\Coroutine;use Swoole\Coroutine\WaitGroup;use function Swoole\Coroutine\run;Runtime::enableCoroutine();// 关键:一键 hook 原生阻塞函数run(function(){$smicrotime(true);$wgnewWaitGroup();for($i0;$i3;$i){$wg-add();Coroutine::create(function()use($wg){sleep(1);// 注意:这是 PHP 原生 sleep,被 hook 成非阻塞$wg-done();});}$wg-wait();printf(3 个原生 sleep(1) 并发,总耗时 %.2fs(被hook成非阻塞,所以≈1s)\n,microtime(true)-$s);});看什么:3个原生sleep(1)本该3秒,结果 ≈1秒。这就是 Swoole 最香的地方:老项目几乎零改造提速。---5.2进程模型(ReactorWorkerTask 三层)标题④:三层分工——收发/干业务/干慢活 大白话:Reactor 线程只负责收发网络包,Worker 进程干业务逻辑,Task 进程干慢活(发邮件、写日志),互不拖累。?php// process_model.phpuse Swoole\Http\Server;$servernewServer(0.0.0.0,9501);$server-set([reactor_num2,// Reactor:只管网络收发,不碰业务worker_num4,// Worker:处理你的业务逻辑task_worker_num2,// Task:处理慢任务]);$server-on(request,function($req,$resp)use($server){$id$server-task([jobsend_email,toab.com]);// 慢活丢给 Task$resp-end(已受理,慢活交给 Task 进程 #$id,Worker 立刻返回\n);// Worker 不等,秒回});$server-on(task,function($server,$taskId,$srcWorker,$data){// Task 进程里执行echoTask 处理:{$data[job]} - {$data[to]}\n;sleep(2);// 模拟慢活returnok;});$server-on(finish,function($server,$taskId,$result){// Task 干完回调echoTask #$taskId 完成:$result\n;});$server-start();看什么:curl http://127.0.0.1:9501/ 立刻返回已受理,而 Task 进程在后台慢慢干 2 秒。慢活不卡主流程。---5.2事件驱动/事件循环(Event Loop/epoll)标题⑤:一个线程,盯着一堆事件,谁来了处理谁 大白话:事件循环一个前台,同时盯着很多扇门(连接/定时器),哪扇门响了就去开哪扇——不用为每扇门派一个人。底层就是Linux 的 epoll。?php// event_loop.phpuse Swoole\Timer;echo进程 .getmypid().:一个事件循环同时管 3 个定时器\n;Timer::tick(1000,fn()print( 事件A:每 1s 触发\n));Timer::tick(2000,fn()print( 事件B:每 2s 触发\n));Timer::tick(3000,fn()print( 事件C:每 3s 触发\n));Timer::after(6500,function(){Timer::clearAll();echo结束\n;});// 注意:全程就这一个进程、一个线程,却同时管着多个事件看什么:一个进程同时按各自节奏触发 A/B/C。这就是 epoll 事件循环:用一个循环复用海量连接,而不是一个连接开一个线程。 Swoole 的高并发就建立在这上面。---5.2I/O 大小/缓存/缓冲/轮询 标题⑥:I/O大小的选择(大块少次 vs 小块多次)大白话:一次读一大块 →I/O 次数少但吃内存;一次读一小块 →省内存但 I/O 次数多。?php// io_size.phpuse Swoole\Coroutine\System;use function Swoole\Coroutine\run;run(function(){System::writeFile(/tmp/big.dat,str_repeat(x,10*1024*1024));// 造 10MB$smicrotime(true);$dataSystem::readFile(/tmp/big.dat);// 整块读:1 次 I/Oprintf(整块读:1 次 I/O,%.3fs,峰值内存 %dMB\n,microtime(true)-$s,strlen($data)/1048576);$smicrotime(true);$fpfopen(/tmp/big.dat,r);$n0;while(!feof($fp)){fread($fp,4096);$n;}// 小块读:多次 I/Ofclose($fp);printf(4KB 小块读:%d 次 I/O,%.3fs,省内存\n,$n,microtime(true)-$s);unlink(/tmp/big.dat);});看什么:整块读1次 I/O 但占10MB 内存;小块读2560次 I/O 但几乎不占内存。按场景权衡。 标题⑦:缓存(Cache)——算过/查过的别重复 大白话:第一次慢慢查,结果存起来,后面直接秒回。?php// cache.phpuse function Swoole\Coroutine\run;functionslowQuery(int$id):string{usleep(100000);return结果#$id;}// 模拟100ms慢查run(function(){$cache[];$getfunction($id)use($cache){if(isset($cache[$id])){echo命中缓存 #$id(秒回)\n;return$cache[$id];}echo未命中,查库 #$id(慢)\n;return$cache[$id]slowQuery($id);};$smicrotime(true);$get(1);$get(1);$get(1);printf(3 次取数据(后2次命中缓存)总耗时 %.2fs(无缓存要0.3s)\n,microtime(true)-$s);});看什么:只有第一次100ms,后两次0ms,总耗时 ≈0.1s。 标题⑧:缓冲(Buffer)——小写攒成大写,减I/O 次数 大白话:别来一条写一次,攒够一批再一次性写。?php// buffer.phpuse function Swoole\Coroutine\run;run(function(){$buf[];$flushfunction()use($buf){if(!$buf)return;echo批量写入 .count($buf). 条(1 次 I/O)\n;$buf[];};for($i1;$i250;$i){$buf[]$i;if(count($buf)100)$flush();// 攒满 100 条刷一次}$flush();// 收尾刷掉剩余echo250 条数据只用了 3 次写入(不缓冲要 250 次)\n;});看什么:250条数据,从250次 I/O 降到3次。写日志、批量入库都该这么干。 标题⑨:轮询(Polling)—别忙等,用事件唤醒 大白话:忙轮询每秒问一百遍好了没(空转烧 CPU);事件驱动挂起睡觉,好了自动叫醒你(CPU0)。?php// polling_vs_event.phpuse Swoole\Coroutine;use Swoole\Coroutine\Channel;use function Swoole\Coroutine\run;run(function(){$chnewChannel(1);Coroutine::create(function()use($ch){// 1 秒后才有数据Coroutine::sleep(1);$ch-push(数据来了);});// 正确姿势:事件驱动,挂起等待,期间 CPU 让给别人$smicrotime(true);$data$ch-pop();// 数据到了自动唤醒printf(事件驱动收到「%s」,等待 %.2fs 期间 CPU≈0%%\n,$data,microtime(true)-$s);// 反面教材(别学):while(true){ if($ready) break; } ——空转把一个核烧到100%});看什么:$ch-pop()优雅地等1秒、CPU 不烧;而忙轮询会把 CPU 干到100%。Swoole 全靠事件驱动,不忙轮询。---5.4性能分析:CPU 剖析/火焰图 标题⑩:给Swoole 进程抓火焰图,找最耗 CPU 的函数 大白话:火焰图就是哪个函数最费 CPU的可视化图,横条越宽越该优化它。 先写一个故意有热点函数的服务:?php// hotpath.phpuse Swoole\Http\Server;$servernewServer(0.0.0.0,9501);$server-set([worker_num1]);functionhotFunction():float{// 故意很耗 CPU,火焰图里会是最宽的条$x0;for($i0;$i5_000_000;$i)$xsqrt($i);return$x;}$server-on(request,function($req,$resp){hotFunction();$resp-end(ok);});$server-start();抓图步骤(用 phpspy,不用改代码、不用重启):#1.启动服务,找到 Worker 进程 PID php hotpath.phpps aux|grep hotpath|grep-v grep # 记下 Worker 的 PID #2.一边持续压测制造负载 wrk-c50-d30s http://127.0.0.1:9501/ #3.phpspy 采样20秒(-H99每秒采样99次)phpspy-H99-pWorker_PID--/tmp/prof.txtsleep20#4.生成火焰图 git clone https://github.com/brendangregg/FlameGraphcat/tmp/prof.txt|./FlameGraph/stackcollapse-phpspy.pl|./FlameGraph/flamegraph.plflame.svg 看什么:浏览器打开 flame.svg,hotFunction 那条会是最宽的——最宽的就是优化重点。---5.4系统调用分析(strace)/off-CPU 分析 标题⑪:strace看进程到底在调什么系统调用 大白话:strace 像窃听器,能看到进程在跟内核要什么(收包、发包、读文件)。 # 找到 Worker PID 后:strace-pWorker_PID-T-tt-f #-T 显示每个系统调用耗时-tt 带时间戳-f 跟踪子进程/线程 看什么:你会看到一堆 epoll_wait、recvfrom、sendto——这就是Swoole 的网络 I/O 循环;epoll_wait 后面跟着1.998这种就是等了 2 秒。 标题⑫:off-CPU分析 ——找进程在等(没烧CPU)的时间 大白话:on-CPU进程在埋头算(火焰图管这个);off-CPU进程在等 I/O/锁/网络,CPU 是空的——很多慢都是慢在等上,但火焰图看不见,得用strace 找。 先写一个故意等很久的接口:?php// offcpu.phpuse Swoole\Http\Server;use Swoole\Coroutine;$servernewServer(0.0.0.0,9501);$server-set([worker_num1]);$server-on(request,function($req,$resp){Coroutine::sleep(2);// 模拟等下游慢接口——纯off-CPU$resp-end(ok\n);});$server-start();分析:strace-pWorker_PID-T-tt21|grep-Eepoll_wait|nanosleep# 然后 curl http://127.0.0.1:9501/看什么:会看到epoll_wait(...)...2.0001,这2秒就是 off-CPU 等待时间(CPU 占用为0,但用户就是等了2秒)。结论:这种慢不能靠优化代码解决,得去优化下游/超时设置。---第5章一句话总览-CPU 密集用多进程,I/O 密集用协程(并发≠并行,①);-协程把阻塞写法变非阻塞,一键 Hook 不改老代码(②③);-Reactor 收包/Worker 干活/Task 干慢活,三层不互相拖(④);-一个事件循环(epoll)复用海量连接,而不是一连接一线程(⑤);-I/O 调大小、加缓存、攒缓冲、用事件别忙轮询(⑥⑦⑧⑨);-on-CPU 用火焰图,off-CPU 用 strace,两手都要看(⑩⑪⑫)。
性能之巅=协程 vs 进程 vs 线程、事件循环 epoll、连接池、火焰图)
---5.2并发与并行(Concurrency vs Parallelism)标题①:协程是并发(一个核轮流干),多进程才是并行(多核同时干)大白话:并发一个人快速切换做多件事(看起来同时);并行多个人真的同时做。协程救不了 CPU 密集型,多进程才行。?php// concurrency_vs_parallelism.phpuse Swoole\Coroutine;use Swoole\Coroutine\WaitGroup;use Swoole\Process;use function Swoole\Coroutine\run;functioncpuWork():int{// 一份纯 CPU 活:数质数$c0;for($n2;$n200000;$n){$ptrue;for($i2;$i*$i$n;$i)if($n%$i0){$pfalse;break;}if($p)$c;}return$c;}// 场景A:协程并发做 4 份 CPU 活 ——没加速,4 份还在抢同 1 个核run(function(){$smicrotime(true);$wgnewWaitGroup();for($i0;$i4;$i){$wg-add();Coroutine::create(function()use($wg){cpuWork();$wg-done();});}$wg-wait();printf(【协程并发】4份CPU活 耗时 %.2fs(没加速,共用1核)\n,microtime(true)-$s);});// 场景B:多进程并行做 4 份 ——4 个核同时跑,约快 4 倍$smicrotime(true);for($i0;$i4;$i){(newProcess(fn()cpuWork()))-start();}for($i0;$i4;$i)Process::wait();printf(【多进程并行】4份CPU活 耗时 %.2fs(真·并行,约快4倍)\n,microtime(true)-$s);看什么:协程版4份活 ≈串行耗时(没加速),多进程版 ≈缩短到1/4。记住:I/O 密集用协程,CPU 密集用多进程。---5.2非阻塞 I/O(Non-Blocking I/O)标题②:协程让阻塞写法自动变非阻塞 大白话:你照样写 sleep、查库这种等的代码,Swoole 在底层偷偷帮你让出 CPU 去干别的。?php// nonblocking.phpuse Swoole\Coroutine;use function Swoole\Coroutine\run;run(function(){Coroutine::create(function(){echo协程1:开始等 2 秒 I/O\n;Coroutine::sleep(2);// 不会卡住整个进程echo协程1:完成\n;});Coroutine::create(function(){for($i1;$i3;$i){Coroutine::sleep(0.5);echo协程2:干活 $i(协程1还在等)\n;}});echo主协程:我没被协程1的2秒卡住\n;});看什么:协程1在等 2 秒的时候,协程2已经干了好几轮活——等待时间被利用起来了。 标题③:一键Hook ——连原生 sleep/curl/mysqli 都变非阻塞 大白话:enableCoroutine()一开,老代码不用改,所有阻塞函数自动协程化。?php// hook.phpuse Swoole\Runtime;use Swoole\Coroutine;use Swoole\Coroutine\WaitGroup;use function Swoole\Coroutine\run;Runtime::enableCoroutine();// 关键:一键 hook 原生阻塞函数run(function(){$smicrotime(true);$wgnewWaitGroup();for($i0;$i3;$i){$wg-add();Coroutine::create(function()use($wg){sleep(1);// 注意:这是 PHP 原生 sleep,被 hook 成非阻塞$wg-done();});}$wg-wait();printf(3 个原生 sleep(1) 并发,总耗时 %.2fs(被hook成非阻塞,所以≈1s)\n,microtime(true)-$s);});看什么:3个原生sleep(1)本该3秒,结果 ≈1秒。这就是 Swoole 最香的地方:老项目几乎零改造提速。---5.2进程模型(ReactorWorkerTask 三层)标题④:三层分工——收发/干业务/干慢活 大白话:Reactor 线程只负责收发网络包,Worker 进程干业务逻辑,Task 进程干慢活(发邮件、写日志),互不拖累。?php// process_model.phpuse Swoole\Http\Server;$servernewServer(0.0.0.0,9501);$server-set([reactor_num2,// Reactor:只管网络收发,不碰业务worker_num4,// Worker:处理你的业务逻辑task_worker_num2,// Task:处理慢任务]);$server-on(request,function($req,$resp)use($server){$id$server-task([jobsend_email,toab.com]);// 慢活丢给 Task$resp-end(已受理,慢活交给 Task 进程 #$id,Worker 立刻返回\n);// Worker 不等,秒回});$server-on(task,function($server,$taskId,$srcWorker,$data){// Task 进程里执行echoTask 处理:{$data[job]} - {$data[to]}\n;sleep(2);// 模拟慢活returnok;});$server-on(finish,function($server,$taskId,$result){// Task 干完回调echoTask #$taskId 完成:$result\n;});$server-start();看什么:curl http://127.0.0.1:9501/ 立刻返回已受理,而 Task 进程在后台慢慢干 2 秒。慢活不卡主流程。---5.2事件驱动/事件循环(Event Loop/epoll)标题⑤:一个线程,盯着一堆事件,谁来了处理谁 大白话:事件循环一个前台,同时盯着很多扇门(连接/定时器),哪扇门响了就去开哪扇——不用为每扇门派一个人。底层就是Linux 的 epoll。?php// event_loop.phpuse Swoole\Timer;echo进程 .getmypid().:一个事件循环同时管 3 个定时器\n;Timer::tick(1000,fn()print( 事件A:每 1s 触发\n));Timer::tick(2000,fn()print( 事件B:每 2s 触发\n));Timer::tick(3000,fn()print( 事件C:每 3s 触发\n));Timer::after(6500,function(){Timer::clearAll();echo结束\n;});// 注意:全程就这一个进程、一个线程,却同时管着多个事件看什么:一个进程同时按各自节奏触发 A/B/C。这就是 epoll 事件循环:用一个循环复用海量连接,而不是一个连接开一个线程。 Swoole 的高并发就建立在这上面。---5.2I/O 大小/缓存/缓冲/轮询 标题⑥:I/O大小的选择(大块少次 vs 小块多次)大白话:一次读一大块 →I/O 次数少但吃内存;一次读一小块 →省内存但 I/O 次数多。?php// io_size.phpuse Swoole\Coroutine\System;use function Swoole\Coroutine\run;run(function(){System::writeFile(/tmp/big.dat,str_repeat(x,10*1024*1024));// 造 10MB$smicrotime(true);$dataSystem::readFile(/tmp/big.dat);// 整块读:1 次 I/Oprintf(整块读:1 次 I/O,%.3fs,峰值内存 %dMB\n,microtime(true)-$s,strlen($data)/1048576);$smicrotime(true);$fpfopen(/tmp/big.dat,r);$n0;while(!feof($fp)){fread($fp,4096);$n;}// 小块读:多次 I/Ofclose($fp);printf(4KB 小块读:%d 次 I/O,%.3fs,省内存\n,$n,microtime(true)-$s);unlink(/tmp/big.dat);});看什么:整块读1次 I/O 但占10MB 内存;小块读2560次 I/O 但几乎不占内存。按场景权衡。 标题⑦:缓存(Cache)——算过/查过的别重复 大白话:第一次慢慢查,结果存起来,后面直接秒回。?php// cache.phpuse function Swoole\Coroutine\run;functionslowQuery(int$id):string{usleep(100000);return结果#$id;}// 模拟100ms慢查run(function(){$cache[];$getfunction($id)use($cache){if(isset($cache[$id])){echo命中缓存 #$id(秒回)\n;return$cache[$id];}echo未命中,查库 #$id(慢)\n;return$cache[$id]slowQuery($id);};$smicrotime(true);$get(1);$get(1);$get(1);printf(3 次取数据(后2次命中缓存)总耗时 %.2fs(无缓存要0.3s)\n,microtime(true)-$s);});看什么:只有第一次100ms,后两次0ms,总耗时 ≈0.1s。 标题⑧:缓冲(Buffer)——小写攒成大写,减I/O 次数 大白话:别来一条写一次,攒够一批再一次性写。?php// buffer.phpuse function Swoole\Coroutine\run;run(function(){$buf[];$flushfunction()use($buf){if(!$buf)return;echo批量写入 .count($buf). 条(1 次 I/O)\n;$buf[];};for($i1;$i250;$i){$buf[]$i;if(count($buf)100)$flush();// 攒满 100 条刷一次}$flush();// 收尾刷掉剩余echo250 条数据只用了 3 次写入(不缓冲要 250 次)\n;});看什么:250条数据,从250次 I/O 降到3次。写日志、批量入库都该这么干。 标题⑨:轮询(Polling)—别忙等,用事件唤醒 大白话:忙轮询每秒问一百遍好了没(空转烧 CPU);事件驱动挂起睡觉,好了自动叫醒你(CPU0)。?php// polling_vs_event.phpuse Swoole\Coroutine;use Swoole\Coroutine\Channel;use function Swoole\Coroutine\run;run(function(){$chnewChannel(1);Coroutine::create(function()use($ch){// 1 秒后才有数据Coroutine::sleep(1);$ch-push(数据来了);});// 正确姿势:事件驱动,挂起等待,期间 CPU 让给别人$smicrotime(true);$data$ch-pop();// 数据到了自动唤醒printf(事件驱动收到「%s」,等待 %.2fs 期间 CPU≈0%%\n,$data,microtime(true)-$s);// 反面教材(别学):while(true){ if($ready) break; } ——空转把一个核烧到100%});看什么:$ch-pop()优雅地等1秒、CPU 不烧;而忙轮询会把 CPU 干到100%。Swoole 全靠事件驱动,不忙轮询。---5.4性能分析:CPU 剖析/火焰图 标题⑩:给Swoole 进程抓火焰图,找最耗 CPU 的函数 大白话:火焰图就是哪个函数最费 CPU的可视化图,横条越宽越该优化它。 先写一个故意有热点函数的服务:?php// hotpath.phpuse Swoole\Http\Server;$servernewServer(0.0.0.0,9501);$server-set([worker_num1]);functionhotFunction():float{// 故意很耗 CPU,火焰图里会是最宽的条$x0;for($i0;$i5_000_000;$i)$xsqrt($i);return$x;}$server-on(request,function($req,$resp){hotFunction();$resp-end(ok);});$server-start();抓图步骤(用 phpspy,不用改代码、不用重启):#1.启动服务,找到 Worker 进程 PID php hotpath.phpps aux|grep hotpath|grep-v grep # 记下 Worker 的 PID #2.一边持续压测制造负载 wrk-c50-d30s http://127.0.0.1:9501/ #3.phpspy 采样20秒(-H99每秒采样99次)phpspy-H99-pWorker_PID--/tmp/prof.txtsleep20#4.生成火焰图 git clone https://github.com/brendangregg/FlameGraphcat/tmp/prof.txt|./FlameGraph/stackcollapse-phpspy.pl|./FlameGraph/flamegraph.plflame.svg 看什么:浏览器打开 flame.svg,hotFunction 那条会是最宽的——最宽的就是优化重点。---5.4系统调用分析(strace)/off-CPU 分析 标题⑪:strace看进程到底在调什么系统调用 大白话:strace 像窃听器,能看到进程在跟内核要什么(收包、发包、读文件)。 # 找到 Worker PID 后:strace-pWorker_PID-T-tt-f #-T 显示每个系统调用耗时-tt 带时间戳-f 跟踪子进程/线程 看什么:你会看到一堆 epoll_wait、recvfrom、sendto——这就是Swoole 的网络 I/O 循环;epoll_wait 后面跟着1.998这种就是等了 2 秒。 标题⑫:off-CPU分析 ——找进程在等(没烧CPU)的时间 大白话:on-CPU进程在埋头算(火焰图管这个);off-CPU进程在等 I/O/锁/网络,CPU 是空的——很多慢都是慢在等上,但火焰图看不见,得用strace 找。 先写一个故意等很久的接口:?php// offcpu.phpuse Swoole\Http\Server;use Swoole\Coroutine;$servernewServer(0.0.0.0,9501);$server-set([worker_num1]);$server-on(request,function($req,$resp){Coroutine::sleep(2);// 模拟等下游慢接口——纯off-CPU$resp-end(ok\n);});$server-start();分析:strace-pWorker_PID-T-tt21|grep-Eepoll_wait|nanosleep# 然后 curl http://127.0.0.1:9501/看什么:会看到epoll_wait(...)...2.0001,这2秒就是 off-CPU 等待时间(CPU 占用为0,但用户就是等了2秒)。结论:这种慢不能靠优化代码解决,得去优化下游/超时设置。---第5章一句话总览-CPU 密集用多进程,I/O 密集用协程(并发≠并行,①);-协程把阻塞写法变非阻塞,一键 Hook 不改老代码(②③);-Reactor 收包/Worker 干活/Task 干慢活,三层不互相拖(④);-一个事件循环(epoll)复用海量连接,而不是一连接一线程(⑤);-I/O 调大小、加缓存、攒缓冲、用事件别忙轮询(⑥⑦⑧⑨);-on-CPU 用火焰图,off-CPU 用 strace,两手都要看(⑩⑪⑫)。