<p>CodeIgniter 不提供原生 Semaphore 机制,因其轻量设计未封装信号量语义;PHP 原生 sem_* 函数仅限 Unix 本地 IPC,不兼容 FPM/Swoole/分布式场景,必须依赖 Redis 或 MySQL 等外部服务实现原子锁。</p>

CodeIgniter 本身不提供 Semaphore(信号量)机制,也不内置分布式锁能力;直接在 CI 中调用 RSemaphore 或 sem_acquire() 会报错或完全无效。
为什么 CodeIgniter 没有原生 Semaphore
CI 是一个轻量级 PHP 框架,其数据库层、缓存层和核心类均未封装信号量语义。PHP 原生的 sem_acquire()、sem_release() 等函数仅适用于 *Unix 系统本地进程间通信*,依赖系统 sysvsem 扩展,无法跨机器、不兼容 FPM/Swoole/CLI 多进程模型,更不能用于 Web 请求间的分布式协调。
- CI 的
$this->cache、$this->db等组件设计目标是单机请求隔离,无跨节点状态同步能力 - 所谓“CI 分布式锁机制”是误传——CI 不负责锁的实现,只提供接入外部锁服务的通道(如手动调用 Redis 或 DB)
- 试图在 CI 控制器里写
sem_get(0x123),在 Docker 容器或 Nginx+PHP-FPM 环境下大概率返回false或直接抛出 Warning
想在 CI 中实现类似 Semaphore 的并发控制,只能靠外部服务
真正可行的做法,是绕过 CI 自身限制,用它作为“胶水”去对接 Redis 或 MySQL 这类支持原子操作的外部存储。关键不是“CI 怎么做”,而是“你如何用 CI 调用底层原子指令”。
- 用 Redis 实现:通过
SETNX+EXPIRE或 Lua 脚本模拟许可计数,再封装成类似acquire()/release()的方法 - 用 MySQL 实现:建一张
semaphores表,字段含name(唯一索引)、permits(当前可用数)、updated_at;每次获取许可前用UPDATE ... SET permits = permits - 1 WHERE name = ? AND permits > 0,检查affected_rows === 1 - 不要手写重试逻辑:CI 的
$this->db->query()不带自动重试,需自行 while 循环 + sleep,但高并发下易打满连接池 - 避免用 CI 的 Query Builder:像
$this->db->where()->update()无法保证“读-改-写”原子性,必须用原生 SQL 或事务包裹
Redisson 的 RSemaphore 能否接入 CodeIgniter
可以,但不是“CI 集成 Redisson”,而是“你在 CI 里实例化 Redisson 客户端并调用它”。这要求:
- 项目已安装
redisson/redissonComposer 包,并启用 Redis 扩展 - 在 CI 的 Autoloader 或自定义 Library 中初始化
RedissonClient,例如:$config = new Config(); $config->useSingleServerConfig()->setAddress('redis://127.0.0.1:6379'); $redisson = Redisson::create($config); - 调用时严格注意作用域:每个请求应使用独立的
RSemaphore实例($sem = $redisson->getSemaphore("user_reg");),不可复用或静态缓存 - 必须显式
$sem->acquire()和$sem->release(),且release()必须在finally块中执行,否则锁泄漏风险极高 - CI 的异常处理机制(如
show_error())不会自动触发release(),这点极易被忽略
真正难的从来不是“怎么写 acquire”,而是“怎么确保 release 一定被执行”——网络超时、PHP Fatal Error、Nginx worker kill、甚至 Redis 连接闪断,都可能导致许可数永久丢失。生产环境若真要用 RSemaphore,务必配合 Redis 的 key 过期策略 + 后台巡检脚本清理滞留锁。


















