php 不支持真正的多线程,pthreads 在 php 7.4+ 已被移除且无法在 php 8.x 使用;推荐方案是 swoole 协程(i/o 密集型高效)或 pcntl 多进程(兼容稳定)。

PHP 本身不支持真正的多线程
直接说结论:pthreads 扩展在 PHP 7.4+ 已被彻底移除,官方不再维护;PHP 8.x 完全无法编译安装它。所谓“PHP 多线程”,要么是旧版(PHP 5.6–7.3)的遗留方案,要么是误用——它依赖 ZTS(Zend Thread Safety)构建,而绝大多数生产环境(包括主流 Docker 镜像、apt 包、Homebrew 安装)默认是 NTS 模式,pthreads 根本加载不了。
常见错误现象:PHP Warning: Module 'pthreads' already loaded in Unknown on line 0 或更常见的 PHP Fatal error: Class 'Thread' not found,本质不是配置问题,而是版本/构建不兼容。
- 别再尝试在 PHP 8 上编译
pthreads——它没有适配,代码已从 GitHub 归档 - 如果你看到“启用 ZTS 后就能用”,请确认:你用的是自己编译的 PHP,且所有扩展(如
curl、pdo_mysql)都重新用 ZTS 模式编译过,否则运行时大概率段错误 - 即使成功,
pthreads的对象共享模型极脆弱:不能传 Closure、Resource、PDO 实例等,一传就崩溃
替代方案只有协程(Swoole / OpenSwoole)或进程模型(pcntl)
真正可用的并发路径只有两条:用 Swoole 的协程(推荐),或用 pcntl_fork 做多进程(传统但稳定)。协程不是“线程”,它是单线程内高密度调度,但对 I/O 密集型任务(HTTP 请求、数据库查询、文件读写)效果接近多线程,且无锁、内存开销小。
使用场景:爬虫、API 聚合、批量消息推送、实时日志处理。
立即学习“PHP免费学习笔记(深入)”;
-
Swoole要求 PHP ≥ 7.4,扩展需单独安装(pecl install swoole),注意关闭opcache.enable_cli=1,否则协程可能不生效 - 别混用
sleep()和协程:必须用co::sleep(),否则整个协程调度器阻塞 -
mysqli/PDO默认不协程安全,得用Swoole\Coroutine\MySQL或Swoole\Coroutine\PDO替代 - 示例片段(并发 10 个 HTTP 请求):
$urls = ['https://httpbin.org/delay/1'] * 10; Swoole\Coroutine\run(function () use ($urls) { $clients = []; foreach ($urls as $url) { $clients[] = go(function () use ($url) { $cli = new Swoole\Coroutine\Http\Client('httpbin.org', 443, true); $cli->set(['timeout' => 5]); $cli->get('/delay/1'); echo "done: {$cli->body}\n"; }); } });
pcntl + proc_open 是最兼容的“伪并行”方案
如果你不能引入扩展,只靠 PHP 标准库,pcntl_fork 或 proc_open 是唯一可行路径。它启动多个独立 PHP 进程,彼此内存隔离,天然避免共享冲突,但进程创建开销大、IPC(进程间通信)麻烦、无法动态扩缩容。
性能影响:10 个并发请求,pcntl 可能比协程慢 3–5 倍(启动进程耗时),但胜在稳定、可调试、不依赖扩展。
- 别在 Web SAPI(如 Apache mod_php、PHP-FPM)里用
pcntl_fork——FPM worker 会异常退出或卡死 - 务必用
pcntl_waitpid(-1, $status)回收子进程,否则变成僵尸进程;proc_open则要显式proc_close() - 子进程不能继承父进程的数据库连接、Redis 实例等资源,必须在子进程中重建
- 参数差异:用
proc_open更可控(可捕获 stdout/stderr),pcntl_fork更轻量但调试困难
协程的坑比想象中多:上下文、超时、错误捕获
协程不是银弹。最大的认知偏差是以为“写了 go() 就自动并发”,其实调度行为高度依赖底层驱动和代码写法。
- 全局变量、静态变量在协程间不隔离——
static $x = 0; $x++在多个协程里会互相覆盖 - 没设超时的协程(如未设置
timeout的HttpClient)可能永久挂起,拖垮整个服务 -
try/catch只捕获当前协程异常,其他协程崩溃不会中断主流程,得靠Swoole\Coroutine::defer()或日志监控兜底 - 协程内调用阻塞函数(如
file_get_contents、sleep、原生cURL)会阻塞整个事件循环,必须换为协程版 API
复杂点不在“怎么启”,而在“怎么稳住”。协程的生命周期、错误传播、资源释放,每一步都得比同步代码多想一层。











