协程中不能使用swoole\lock,因其仅作用于进程/线程层,会导致协程假死;应改用swoole\coroutine\rwlock、channel或atomic等协程安全同步原语,分布式场景须用redis分布式锁。

协程里不能用 Swoole\Lock
协程调度器本身不感知 Swoole\Lock,它只在进程/线程层面起作用。你在协程里调用 $lock->lock(),会阻塞整个协程,但调度器仍认为该协程“可运行”,不会切换出去——结果就是:其他协程也被卡住,看起来像死锁,实际是误用。
典型错误现象:[fatal error]: all coroutines (count: 1) are asleep - deadlock!,但你根本没写 sleep() 或 Channel::pop(),只是加了个 Swoole\Lock。
- 协程中必须用协程安全的同步原语,比如
Swoole\Coroutine\Channel、Swoole\Coroutine\WaitGroup、Swoole\Atomic -
Swoole\Lock只适用于多进程模型下的临界区保护(如 Worker 进程间共享文件计数),且必须确保 lock/unlock 在同一进程内成对出现 - 若你在
onRequest回调里 new 一个Swoole\Lock,每次请求都新建对象,不仅无效,还会泄漏内存
协程间共享变量该用什么替代锁?
协程天然串行执行(单个协程内无抢占),所以「同一个协程内」访问全局变量或静态变量是安全的;但跨协程并发读写就危险了——不是因为竞态本身难解决,而是很多人第一反应就想套个锁,反而掉进陷阱。
推荐优先级从高到低:
- 用
Swoole\Atomic:适合整数计数类场景(如请求计数、开关状态),底层基于 CPU 原子指令,无调度开销,$atomic->add(1)直接生效 - 用
Swoole\Coroutine\Channel做协调:比如把“修改共享状态”的操作封装成任务,投递到一个专用协程里顺序处理,其他协程只发消息不直接改数据 - 用
Swoole\Coroutine\RWLock(v6.1+):真正为协程设计的读写锁,支持lockRead()/lockWrite()和超时参数,比自己手写 Channel 调度更简洁
注意:Swoole\Coroutine\RWLock 的 lockWrite() 是阻塞式协程挂起,不是进程级阻塞,调度器能正常切走——这和 Swoole\Lock 有本质区别。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
Swoole\Coroutine\RWLock 怎么用才不出错?
它不是拿来保护任意代码块的“万能锁”,而是一个明确的协作契约:所有协程必须约定好谁读、谁写,并主动配合超时与释放。
- 必须在
go()启动协程前初始化锁对象,否则可能被 GC 提前回收 - 加锁后务必配对
unlock(),建议用try/finally包裹,避免异常跳过释放 - 永远传超时值:
$rwlock->lockWrite(3.0),而不是$rwlock->lockWrite();返回false时要主动退出或重试,不能硬等 - 禁止在 lock 区域内调用可能触发协程切换的 API,比如
Co::sleep()、MySQL::query()、Redis::get()——这些会让锁长期持有,阻塞其他协程
示例片段:
$rwlock = new Swoole\Coroutine\RWLock();
go(function () use ($rwlock) {
if ($rwlock->lockWrite(2.0)) {
try {
// 修改共享配置、缓存等耗时短的操作
file_put_contents('/tmp/config.json', json_encode(['status' => 'updating']));
} finally {
$rwlock->unlock();
}
} else {
echo "acquire write lock timeout\n";
}
});
分布式场景下别硬扛,直接上 Redis 锁
当多个 Swoole Worker 进程(甚至跨机器)要协调访问同一资源,协程锁或进程锁都失效了——这时必须升级到分布式锁,而不要试图用 Atomic 或 Channel 撑场面。
关键点不在“怎么写”,而在“怎么防崩”:
- 锁 key 必须带唯一标识(如
worker:getmypid():task_123),避免不同进程误删彼此锁 - 永远用
SET的NX EX组合,不用SETNX + EXPIRE两步,否则有竞态窗口 - 释放锁必须用 Lua 脚本校验 value 再删,防止 A 进程锁过期后被 B 获取,B 过期后 A 还去删,误删 B 的锁
- 业务逻辑执行时间必须明显小于锁过期时间(建议 ≤ 1/3),否则靠续期(renew)也容易出乱子
一句话:协程锁管单进程内协作,Redis 锁管跨进程/跨机器共识——边界不清,问题就藏在交接处。










