swoole\table 是多 worker 进程间共享内存最实用的方案,但需手动处理锁、类型限制、容量上限及生命周期管理;set() 无锁导致覆盖写,仅 type_int 支持原子增减,lock() 非协程安全且须避免长耗时操作,创建参数易错于行数取整、字符串字节长度与总内存超 128mb,destroy() 必须显式调用否则内存不释放。

直接说结论: Swoole\Table 是多 Worker 进程间共享内存最实用的方案,但它不是“开箱即用就安全”的黑盒——set() 和 get() 默认不加锁,字段类型决定能否原子操作,容量不可扩容,且生命周期需手动管理。
为什么 Table 的 set() 会丢数据?
因为 set() 默认无锁。两个 Worker 同时对同一 $key 执行 set(),后执行的会覆盖前执行的,典型表现是在线人数忽高忽低、库存扣减失败。
- 只有
TYPE_INT字段能用incr()/decr()实现原子增减;字符串或浮点字段必须显式调用lock($key)+unlock($key)包裹读-改-写逻辑 -
lock($key)是行级自旋锁,阻塞式,不适合长耗时操作;若临界区里有网络请求或 sleep,会拖垮整个 Worker - 不要在协程中调用
lock()—— Swoole\Table 的锁不是协程安全的,只适用于进程/线程粒度
创建 Table 时最容易填错的三个参数
创建失败或运行时报错,90% 出在这三处:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
-
new Swoole\Table(1024)中的1024是预估最大行数,实际分配会向上取整为最近的 2 的幂(如传 1000 → 实际 1024),但超 21 亿行会截断,别硬凑大数 -
column('name', Swoole\Table::TYPE_STRING, 64)的64是字节长度,不是字符数;UTF-8 下一个中文占 3 字节,64 字节最多存 21 个汉字,超长会被静默截断 - 总内存不能超 128MB:单行大小 = 所有字段 size 总和(
TYPE_INT固定 8 字节,TYPE_FLOAT固定 8 字节,TYPE_STRING为指定 size),行数 × 单行大小 > 128MB 就会create()失败
Table 生命周期不清理,内存就真不释放
Swoole\Table 基于 mmap,不是 PHP 堆内存,unset 或进程正常退出都不会自动释放它。
- 必须在
onShutdown或onWorkerStop中显式调用$table->destroy(),否则重启服务后旧内存块仍驻留,表现为shmop_open(): unable to open or create shared memory segment - 进程被
kill -9崩溃时,操作系统会回收,但这不可控、不可测,不能作为清理策略 - 在 Think-Swoole 等框架中,
app("swoole.table.xxx")返回的是封装对象,其底层destroy()是否触发取决于框架实现,务必查清文档或源码
Atomic 能替代 Table 吗?看场景
不能一概而论。Atomic 是 int64 单值原子计数器,快到纳秒级,但能力极窄:
- 适合纯计数:请求总数、在线人数(仅数字)、限流令牌桶剩余数
- 不适合结构化数据:比如要同时更新
last_login_time和login_count,Atomic 无法存时间戳,也没字段概念 - 不支持条件写入:
compareAndSet类操作得自己用 Table + lock + if 判断模拟,Atomic 本身没这个 API - 误用
Swoole\Atomic存字符串会直接报Fatal error: Call to undefined method Swoole\Atomic::strval()
真正难的不是初始化 Table,而是想清楚哪一行数据需要锁、锁多久、谁来负责释放、崩溃后如何兜底。共享内存不是银弹,它把并发复杂性从数据库推到了应用层,你得亲手接住。










