num_threads应根据CPU核心数和应用类型设定:CPU密集型建议≤核心数,I/O密集型可设至核心数2倍;8核服务器推荐8–12,需满足num_threads×memory_limit<可用内存,避免OOM。

num_threads设多少取决于CPU核心数和应用类型
直接看服务器CPU核心数,不是看QPS或并发量。FrankenPHP的num_threads是OS级线程,每个线程默认绑定一个调度单元;设多了不提升吞吐,反而加剧上下文切换和内存争用。
常见错误现象:curl请求卡在等待、frankenphp_queue_depth持续上涨、load average飙升但cpu idle仍很高——这往往不是线程不够,而是线程太多导致PHP进程集体阻塞在I/O或锁上。
- 8核服务器:起步设
num_threads 8,保守可到12;纯CPU密集型(如图像压缩、JWT签名)建议≤CPU核心数 - I/O密集型(大量HTTP API调用、数据库查询):可设到
num_threads 16,但必须配合max_wait_time防请求无限排队 - 容器部署时,要满足:
num_threads × memory_limit < 容器可用内存。例如容器限制512MB、memory_limit=256M,那num_threads最多只能是2
不要依赖默认值,尤其在生产环境
FrankenPHP默认是num_threads = CPU核心数 × 2,比如16核机器起32个线程——这对大多数PHP应用来说太高了。Laravel或WordPress这类框架单请求常驻内存20–50MB,32线程轻松吃掉1GB+,还没算Go runtime和OPcache开销。
实测中,很多团队把num_threads从默认32降到12后,P95延迟下降40%,OOM事件归零。
立即学习“PHP免费学习笔记(深入)”;
- 本地开发可设
num_threads 2或4,省资源也够用 - 上线前必须压测:用
k6模拟真实流量,观察frankenphp_busy_threads和frankenphp_total_threads差值是否长期为0 - 如果差值长期为0,说明线程全在等外部响应,不是真忙——这时加线程没用,该优化SQL或API调用
配置位置和生效方式要对得上
num_threads必须写在frankenphp全局块里,不是php_server或worker子块中。写错位置会导致配置被忽略,FrankenPHP仍按默认值启动。
正确示例:
{ frankenphp {
num_threads 12
max_threads 24
max_wait_time 10s
php_ini memory_limit 256M
}
}
- 修改后需重启FrankenPHP进程,热重载不支持
num_threads变更 - 可通过
ps -eLf | grep frankenphp | wc -l粗略验证线程数(注意含Go主线程) - 更准的方式是访问
/metrics端点,查frankenphp_total_threads指标
和max_threads的关系别搞混
num_threads是常驻线程池大小,服务一启动就分配好;max_threads是它之上允许动态扩容的“天花板”。两者不是相等关系,也不是倍数强制关系。
容易踩的坑:
- 设
num_threads 4却漏配max_threads,结果默认max_threads=4,突发流量直接排队超时 - 设
num_threads 16又配max_threads auto,高峰时线程飙到64,内存瞬间打满被OOM Killer干掉 - 误以为
max_threads越高越稳,其实它只解决短时尖峰,不能替代num_threads的合理规划
真正关键的永远是num_threads——它决定了基础服务能力,也框定了内存底线。max_threads只是缓冲垫,垫太厚反而让问题延迟暴露。



















