shmop不能直接原子写,因其仅提供裸内存读写且无锁机制,多进程同时write会覆盖;需apcu的apcu_cas或redis的watch/multi等底层cas支持才能保障原子性。

为什么不能直接用 shmop 做原子写?
PHP 自带的 shmop 扩展只提供裸内存读写,没有锁机制。多个进程同时 shmop_write 同一段内存时,会相互覆盖,根本谈不上“原子性”。哪怕你用 flock 套一层文件锁,也解决不了跨进程内存映射的竞态——因为 shmop 不保证写操作的 CPU 级原子性,更不提供 compare-and-swap(CAS)这类原语。
composer require pecl/igbinary 不是正解,真正要装的是 ext-apcu 或 ext-redis
共享内存的原子读写必须依赖底层支持 CAS 的存储引擎。APCu 提供 apcu_cas 和 apcu_inc,Redis 提供 INCR、GETSET、WATCH/MULTI/EXEC。Composer 只能帮你装 PHP 包,但无法绕过扩展依赖:
- APCu 必须启用
apc.enable_cli=1(CLI 场景),且仅限单机; - Redis 需部署独立服务,但支持分布式和 TTL;
- 别试图用
igbinary或msgpack替代原子性——它们只是序列化工具,不提供并发控制。
用 apcu_cas 实现计数器自增的最小可靠写法
APCu 的 apcu_cas 是唯一接近硬件级原子性的 PHP 原生方案,但它要求先 apcu_fetch,再计算新值,最后用旧值比对提交。失败就重试:
$key = 'counter';
do {
$old = apcu_fetch($key, $success);
$new = $success ? $old + 1 : 1;
} while (!apcu_cas($key, $old, $new));
注意三点:
- 不能省略
$success参数判断,否则apcu_fetch返回false时会被当成 0; - 重试无上限,高并发下可能饥饿,需加
usleep(10)防止忙等; -
apcu_cas只对整数/字符串有效,数组或对象需序列化,但序列化后 CAS 失效——必须用扁平结构。
Redis 的 WATCH 在 PHP 中容易误用的点
很多人以为 WATCH + MULTI 就是“事务”,其实它只是乐观锁:一旦被监控 key 被其他客户端修改,EXEC 直接返回 false,不是抛异常。
典型错误写法:
$redis->watch('balance');
$balance = $redis->get('balance');
if ($balance >= $amount) {
$redis->multi()->decrBy('balance', $amount)->exec(); // ❌ 没检查 exec 返回值
}
正确做法必须检查 exec() 结果:
do {
$redis->watch('balance');
$balance = (int)$redis->get('balance');
if ($balance multi()
->decrBy('balance', $amount)
->exec();
} while ($result === false);
关键细节:
-
WATCH必须在MULTI前调用,且每次重试都要重新WATCH; -
EXEC返回false表示被踢出,不是网络错误,不能catch异常来处理; - 不要在
WATCH后执行耗时操作(比如查 DB),否则冲突概率陡增。
真正难的不是语法,是理解“乐观锁”本质:它不阻塞,只验证,失败成本由你承担。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











