---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 活:数质数$c=0;for($n=2;$n<200000;$n++){$p=true;for($i=2;$i*$i<=$n;$i++)if($n%$i===0){$p=false;break;}if($p)$c++;}return$c;}// 场景A:协程"并发"做 4 份 CPU 活 ——没加速,4 份还在抢同 1 个核run(function(){$s=microtime(true);$wg=newWaitGroup();for($i=0;$i<4;$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 倍$s=microtime(true);for($i=0;$i<4;$i++){(newProcess(fn()=>cpuWork()))->start();}for($i=0;$i<4;$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($i=1;$i<=3;$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(){$s=microtime(true);$wg=newWaitGroup();for($i=0;$i<3;$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进程模型(Reactor+Worker+Task 三层)标题④:三层分工——收发/干业务/干慢活 大白话:Reactor 线程只负责"收发网络包",Worker 进程干"业务逻辑",Task 进程干"慢活(发邮件、写日志)",互不拖累。<?php// process_model.phpuse Swoole\Http\Server;$server=newServer('0.0.0.0',9501);$server->set(['reactor_num'=>2,// Reactor:只管网络收发,不碰业务'worker_num'=>4,// Worker:处理你的业务逻辑'task_worker_num'=>2,// Task:处理慢任务]);$server->on('request',function($req,$resp)use($server){$id=$server->task(['job'=>'send_email','to'=>'a@b.com']);// 慢活丢给 Task$resp->end("已受理,慢活交给 Task 进程 #$id,Worker 立刻返回\n");// Worker 不等,秒回});$server->on('task',function($server,$taskId,$srcWorker,$data){// Task 进程里执行echo"Task 处理:{$data['job']} -> {$data['to']}\n";sleep(2);// 模拟慢活return"ok";});$server->on('finish',function($server,$taskId,$result){// Task 干完回调echo"Task #$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$s=microtime(true);$data=System::readFile('/tmp/big.dat');// 整块读:1 次 I/Oprintf("整块读:1 次 I/O,%.3fs,峰值内存 %dMB\n",microtime(true)-$s,strlen($data)/1048576);$s=microtime(true);$fp=fopen('/tmp/big.dat','r');$n=0;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=[];$get=function($id)use(&$cache){if(isset($cache[$id])){echo"命中缓存 #$id(秒回)\n";return$cache[$id];}echo"未命中,查库 #$id(慢)\n";return$cache[$id]=slowQuery($id);};$s=microtime(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=[];$flush=function()use(&$buf){if(!$buf)return;echo"批量写入 ".count($buf)." 条(1 次 I/O)\n";$buf=[];};for($i=1;$i<=250;$i++){$buf[]=$i;if(count($buf)>=100)$flush();// 攒满 100 条刷一次}$flush();// 收尾刷掉剩余echo"250 条数据只用了 3 次写入(不缓冲要 250 次)\n";});看什么:250条数据,从250次 I/O 降到3次。写日志、批量入库都该这么干。 标题⑨:轮询(Polling)—别忙等,用事件唤醒 大白话:忙轮询=每秒问一百遍"好了没"(空转烧 CPU);事件驱动=挂起睡觉,好了自动叫醒你(CPU=0)。<?php// polling_vs_event.phpuse Swoole\Coroutine;use Swoole\Coroutine\Channel;use function Swoole\Coroutine\run;run(function(){$ch=newChannel(1);Coroutine::create(function()use($ch){// 1 秒后才有数据Coroutine::sleep(1);$ch->push("数据来了");});// 正确姿势:事件驱动,挂起等待,期间 CPU 让给别人$s=microtime(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;$server=newServer('0.0.0.0',9501);$server->set(['worker_num'=>1]);functionhotFunction():float{// 故意很耗 CPU,火焰图里会是最宽的条$x=0;for($i=0;$i<5_000_000;$i++)$x+=sqrt($i);return$x;}$server->on('request',function($req,$resp){hotFunction();$resp->end("ok");});$server->start();抓图步骤(用 phpspy,不用改代码、不用重启):#1.启动服务,找到 Worker 进程 PID php hotpath.php&ps aux|grep hotpath|grep-v grep # 记下 Worker 的 PID #2.一边持续压测制造负载 wrk-c50-d30s http://127.0.0.1:9501/ .phpspy 采样20秒(-H99=每秒采样99次)phpspy-H99-p<Worker_PID>-->/tmp/prof.txt&sleep20#4.生成火焰图 git clone https://github.com/brendangregg/FlameGraphcat/tmp/prof.txt|./FlameGraph/stackcollapse-phpspy.pl|./FlameGraph/flamegraph.pl>flame.svg 看什么:浏览器打开 flame.svg,hotFunction 那条会是最宽的——最宽的就是优化重点。---5.4系统调用分析(strace)/off-CPU 分析 标题⑪:strace看进程到底在调什么系统调用 大白话:strace 像"窃听器",能看到进程在跟内核要什么(收包、发包、读文件)。 # 找到 Worker PID 后:strace-p<Worker_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;$server=newServer('0.0.0.0',9501);$server->set(['worker_num'=>1]);$server->on('request',function($req,$resp){Coroutine::sleep(2);// 模拟"等下游慢接口"——纯off-CPU$resp->end("ok\n");});$server->start();分析:strace-p<Worker_PID>-T-tt2>&1|grep-E'epoll_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、连接池、火焰图)
张小明
前端开发工程师
树莓派蜂鸣器避坑指南:有源无源怎么选?GPIO驱动电路要注意什么?
树莓派蜂鸣器实战指南:选型策略与电路设计精要第一次接触树莓派蜂鸣器时,我被货架上那些看似相同的小圆柱体搞糊涂了——它们价格相差数倍,有的标注"有源",有的写着"无源"。直到亲手烧毁两个GPIO引脚后&#…
数据切分本质:时间序列划分与分层抽样的工程实践指南
1. 项目概述:为什么“怎么切数据”比“怎么建模”更值得花三倍时间 在数据科学项目里,我见过太多人把80%精力扑在调参、换模型、堆特征上,结果上线后效果波动大、线上A/B测试不显著、甚至被业务方一句“这结果和我们凭经验猜的差不多”直接打…
C#编写的多门店零售管理系统(含可直接运行的SQL Server数据库)
本文还有配套的精品资源,点击获取 简介:这是一套开箱即用的连锁门店桌面管理工具,用C# WinForms开发,适配本地SQL Server环境。系统支持多个门店的基础资料录入与维护,涵盖商品分类、SKU管理、实时库存查询与盘点、…
GPT-4稀疏激活真相:1.8万亿参数与2%每Token的工程解构
1. 项目概述:参数规模与稀疏激活的真相拆解“GPT-4 Has 1.8 Trillion Parameters. It Uses 2% of Them Per Token.”——这句话过去两年在技术社区反复刷屏,常被当作“大模型已突破算力瓶颈”的佐证,也常被误读为“GPT-4每次推理只调用360亿参…
CSDN AI数字营销服务归属之谜:从ICP备案、软著登记到营收分账路径的全链路穿透分析
更多请点击: https://kaifayun.com 第一章:CSDN AI 数字营销是 CSDN 官方自营服务吗? CSDN AI 数字营销是 CSDN 平台官方推出的、由 CSDN 运营团队直接建设与管理的数字营销服务产品,具备明确的品牌归属、技术栈统一性及服务闭环…
ncmdumpGUI:3步解锁网易云音乐NCM格式的终极免费转换工具
ncmdumpGUI:3步解锁网易云音乐NCM格式的终极免费转换工具 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾在网易云音乐下载了心爱的歌曲&a…