php共享内存必须使用shmop系列函数,因原生不支持posix共享内存;需注意键唯一性、大小固定、手动同步(信号量或文件锁)、序列化处理及替代方案如apcu/redis。

PHP共享内存函数必须用shmop系列
PHP原生不支持POSIX共享内存(如shmget/shmat),只能用shmop_open、shmop_read、shmop_write这一套。别试图用pcntl_fork配合普通变量或apcu_store来“模拟”共享内存——前者变量完全隔离,后者是缓存而非进程间共享内存。
常见错误现象:shmop_open返回false但没报错;shmop_write写入后其他进程读不到;反复调用shmop_open导致段泄漏。
-
shmop_open的$key参数必须是系统级唯一整数(如0x1234),不能用字符串或随机数,否则跨进程无法定位同一段 - 大小必须提前预估并固定,
shmop_open不支持动态扩容,写超会静默截断 - 写入前必须用
shmop_size确认当前段长度,避免越界;读取时也要检查返回值是否为false或空字符串 - 进程退出前务必调用
shmop_delete+shmop_close,否则段残留,ipcs -m能看到“nattch=0 but not destroyed”
高并发下shmop_write不是原子操作
多个PHP-FPM子进程同时shmop_write同一块内存,会出现数据覆盖或错位,因为该函数底层只是memcpy,没有锁机制。
解决办法只有加外部同步:要么用sem_acquire配sem_get信号量,要么用文件锁(flock)兜底。但注意:sem_*函数在PHP 8.0+默认禁用,需编译时启用--enable-sysvsem,且信号量键值也得和共享内存键值保持逻辑关联。
立即学习“PHP免费学习笔记(深入)”;
- 推荐组合:
sem_get($shm_key + 1)+sem_acquire,避免信号量键冲突 - 不要在
shmop_write前后sleep或做耗时操作,否则锁持有时间过长,成为瓶颈 - 如果只是计数器场景,优先考虑
apcu_inc(线程安全)或Redis INCR,比手动管理shmop+sem轻量得多
shmop不支持结构化数据,序列化要自己处理
shmop_write只接受String,你不能直接写Array或Object。必须用serialize/unserialize或json_encode/json_decode,但要注意:
-
serialize结果含PHP版本和类名信息,不同PHP版本间可能反序列化失败;json_encode更安全,但不支持资源、闭包、循环引用 - 写入前必须检查序列化后长度是否≤共享内存段大小,否则
shmop_write返回0且无提示 - 读取后
unserialize前应先校验字符串非空、非false,并用mb_strlen确认长度,防止被截断的脏数据引发致命错误
替代方案比硬啃shmop更实用
真正高并发生产环境几乎不用shmop:它难调试、无持久化、无自动清理、不兼容容器化部署(/dev/shm挂载点受限)、PHP扩展依赖强。
更现实的选择:
- 短生命周期计数/开关:用
apcu_store('flag', true, 30),APCu在PHP-FPM多进程间共享,且带TTL - 需要跨机器:上Redis,哪怕单节点,
INCR/GETSET原语比自己实现锁可靠得多 - 纯本地高频通信(如Worker进程协同):改用unix Domain Socket或消息队列(e.g. Beanstalkd),语义清晰、可监控、易扩缩
shmop只适合极少数场景:比如一个常驻PHP守护进程(非FPM)与若干子进程交换固定格式二进制状态,且对毫秒级延迟敏感——这种需求本身已非常边缘。