本文目录导读:

在 PHP 中实现无锁结构(Lock-Free Structure)确实是一个相对进阶的话题,首先需要明确一个核心事实:PHP 的单线程模型和请求生命周期决定了它天然适合无锁编程,因为每个请求是独立的,不存在多线程竞争问题。
如果你想在 CLI 长驻进程(如 pcntl 多进程)或使用 Swoole / Workerman 等多线程/协程环境下实现无锁,可以从以下几个层面来考虑:
核心思路:利用 atomic 或 CAS(比较交换)
所谓无锁,本质上是依赖 CPU 的原子指令(如 CAS),PHP 本身不直接提供 CAS,但可以通过扩展来实现:
-
推荐扩展:
swoole的Atomic类-
Swoole 提供了
Swoole\Atomic,底层是 C 语言实现的原子操作。 -
代码示例(更安全,推荐在 Swoole 环境下使用):
$atomic = new Swoole\Atomic(0); // 例如实现一个无锁计数器(cli 模式) // 多进程时可以安全使用 $result = $atomic->add();
-
-
替代方案:
parallel扩展(>=7.2, 实验性质)- 提供了
parallel\Runtime,底层也支持共享对象,但同样需要依赖原语。
- 提供了
纯 PHP 的“伪无锁”:基于共享内存 + flock
如果你不想依赖 Swoole,想要纯 PHP 实现,通常做不到真正的 CAS,但如果你指的是 避免互斥锁(flock)导致的阻塞,可以采用 基于文件锁的乐观锁 思路:
-
乐观锁实现(CAS 模拟):适用于数据库或文件存储。
function updateBalance(int $userId, int $newBalance) { $cache = new Redis(); $pipeline = $cache->multi(); // 核心:利用 Redis 的 WATCH 实现 CAS $cache->watch('balance:' . $userId); $currentBalance = $cache->get('balance:' . $userId); if ($currentBalance === false || $newBalance < 0) { $cache->unwatch(); return false; // 失败 } // 尝试写入 $result = $cache->multi() ->set('balance:' . $userId, $newBalance) ->exec(); return $result !== false; // 如果返回值不是 false 则成功 }这是经典的 WATCH/MULTI/EXEC 事务模式,提供了 CAS 语义。
针对无共享内存的“无锁”设计
PHP 最常见的“无锁”其实是 并发隔离,这比“无锁数据”更简单高效:
- 按请求隔离:利用一个全局数组(如
$_SERVER或自定义静态变量),然后在register_shutdown_function中统一落盘。 - Actor 模型(如 Workerman +
Channel):每个 Worker 进程内部使用自己的内存,进程间通过消息传递。消息传递消除锁竞争,这通常比共享内存锁更快。
Example (Workerman 消息通道):
$worker = new Workerman\Worker();
$worker->count = 4; // 多个进程
$channel = new Workerman\Channel\Client('127.0.0.1', 2206);
$worker->onMessage = function($conn, $data) use ($channel) {
// 在这个闭包内,变量 $data 是本进程独有的,无需加锁
// 如果要将数据发送给其他进程或服务,只需 $channel->push('event', $data);
};
避开锁的终极方案:Atomic 文件写
如果真的需要跨进程(如多个 CLI 脚本)写一个日志文件,可以使用 O_APPEND + file_put_contents 的原子特性(单行写入小于 4K 时 POSIX 保证原子性):
file_put_contents('/var/log/app.log', $logMessage, FILE_APPEND | LOCK_EX);
LOCK_EX 实际上是一个独占锁,但它在写入极短时性能损失可忽略,优于 flock 长期持有的阻塞锁。
总结建议
| 场景 | 推荐做法 | 是否真正无锁 |
|---|---|---|
| 高并发 Web 请求 | 使用 Redis / Nginx + APCu(如 apcu_fetch 做原子递增) |
是(APCu 底层是 CAS) |
| Swoole 常驻进程 | 使用 Swoole\Atomic |
是(原子操作) |
| 跨流程(非常少见) | System V 共享内存 (shmop) + sem_acquire |
否(信号量相当于锁) |
| 纯 PHP 不允许扩展 | 用数据库乐观锁(UPDATE ... WHERE 判断版本号) |
语义上是 CAS |
重点提醒
在 PHP 中,真正的“无锁”通常意味着“无阻塞”而非“无锁”,应用层的最佳实践是让数据尽量不要共享,而不是去实现复杂的无锁链表/哈希表(那是 C++/Rust 的领域),使用 Redis 这样的外部存储,把锁的粒度移动到网络层,已经是 PHP 场景下最常用的“伪无锁”方案。
如果你的场景是严格的性能敏感型(如百万并发计数器),建议走出 PHP,考虑使用 Nginx 的 ngx_http_redis 模块或直接用 Swoole 的原子计数,这两种都是底层 C 语言的原子操作。