ngx_shmtx_lock采用“自旋+事件唤醒”机制实现共享内存互斥:先原子CAS抢锁,失败则自旋指定次数,超时后挂起进程;解锁时置lock为0并异步唤醒等待者。

要理解 Nginx 源码中 ngx_shmtx_lock 的执行序列,核心是抓住它在共享内存(shared memory)场景下如何实现“轻量级自旋锁 + 进程间同步”的协作逻辑。它不依赖系统 mutex 或信号量,而是用一个原子变量(lock 字段)配合 CPU 自旋 + 事件唤醒机制,在多 worker 进程争抢同一块共享内存资源时保证互斥。
锁结构体与共享内存布局是前提
Nginx 的共享内存锁基于 ngx_shmtx_t 结构,通常嵌入在共享内存区(如 ngx_slab_pool_t 开头或模块自定义的 shm 区)中。关键字段包括:
-
lock:指向一个
uint32_t *,即实际的锁变量地址(值为 0 表示空闲,1 表示已加锁) - log:用于错误日志上下文
- sem(仅限支持信号量的平台):备用阻塞机制,Linux 下通常不用
-
spin:自旋次数上限(如
ngx_cacheline_size * 2048),防止空转过久
注意:该锁必须被所有 worker 进程映射到**同一虚拟地址**(通过 mmap(MAP_FIXED) 或 slab 分配器保证),否则 *lock 指向不同物理内存,同步失效。
ngx_shmtx_lock 的典型执行流程
调用 ngx_shmtx_lock(&shm->mutex) 后,主要走以下路径(以非信号量模式、Linux x86-64 为例):
- 先尝试原子 CAS(
ngx_atomic_cmp_set)将*lock从 0 设为 1;成功则立即返回,获得锁 - 失败则进入自旋循环:每次循环做两件事:
– 调用ngx_cpu_pause()(对应pause指令),降低功耗并提示 CPU 当前是忙等待
– 检查自旋计数是否超限(spin-- );未超限则继续 CAS 尝试 - 自旋耗尽后,若仍没抢到锁,则调用
ngx_shmtx_wait()—— 此时才真正挂起当前进程:
– 将当前进程的epoll_event(或kqueue等)注册到一个“伪就绪事件”上(本质是利用 event loop 的 wait 机制模拟阻塞)
– 实际不写 fd,而是靠锁释放时主动调用ngx_post_event()唤醒等待队列中的进程
锁释放 ngx_shmtx_unlock 如何触发唤醒
ngx_shmtx_unlock 不只是把 *lock = 0,关键还在于通知等待者:
- 先原子地将
*lock置为 0 - 检查是否有进程在等待(
wait != NULL);若有,则调用ngx_event_process_post()把唤醒事件插入当前 cycle 的 post 队列 - 在下一个 event loop 迭代开始前,post 队列被处理,对应 worker 的
ngx_shmtx_wake()被调用,将其从等待状态移出,重新尝试获取锁
这种“自旋 → 事件挂起 → 异步唤醒”设计,平衡了低争用下的零延迟和高争用下的 CPU 友好性。
调试与验证的小技巧
想确认某处是否真走到了等待路径,可在源码中临时加日志(如 ngx_log_debug0):
- 在
ngx_shmtx_wait入口打点,观察是否频繁进入(说明锁争用严重) - 在
ngx_shmtx_unlock中打印wait ? "waking" : "no waiter" - 用
gdbattach 到 worker 进程,断点设在ngx_shmtx_lock,查看$rax(CAS 返回值)和*lock内存值变化
注意:生产环境避免加日志,因其本身会加剧锁竞争;调试建议用单 worker(worker_processes 1)复现逻辑,再逐步放开。










