当前位置: 首页 > postgresql, 锁模块 > 正文

这是一次完全剥离所有修辞、抛弃一切应用层概念的纯物理级推演。

我们将直接下潜到 PostgreSQL 内核共享内存区,仅使用 C 语言的 PGPROC(进程状态)LOCK(资源实体)PROCLOCK(持有契约) 这三大核心结构体,以及底层的双向链表(Doubly Linked List)和操作系统信号量(Semaphore),把死锁的完整生命周期,按 CPU 执行的绝对物理时序,逐帧推导出来。


第一帧:合法占有与指针的确立(T0 时刻)

【内存状态基线】

系统中有两个并发进程:PGPROC_1PGPROC_2

有两张表被并发访问,在哈希网中对应两个资源结构体:LOCK_ALOCK_B

  1. PGPROC_1 的物理动作:成功申请 LOCK_A 的排他锁。
    • 内存管理器 mallocPROCLOCK_1A
    • 状态字段 PROCLOCK_1A->holdMask 记录 8 级锁,标记 granted = true
    • 指针挂载:该契约被双向插入到 LOCK_A->procLocks 链表和 PGPROC_1->myProcLocks 链表中。
  2. PGPROC_2 的物理动作:成功申请 LOCK_B 的排他锁。
    • 内存管理器 mallocPROCLOCK_2B,标记 granted = true
    • 指针挂载:被双向插入到 LOCK_B->procLocksPGPROC_2->myProcLocks 链表中。

第二帧:交叉碰撞与物理挂起(死锁的绝对形成)

【物理动作:PGPROC_1 申请 LOCK_B

  1. PGPROC_1 的 CPU 提取 LOCK_B->grantMask 进行按位与运算,结果不为 0(因为 PGPROC_2 正拿着排他锁)。冲突成立。
  2. 生成排队契约 PROCLOCK_1B,标记 granted = false,挂入 LOCK_B->procLocks 链表。
  3. 关键指针修改:内核修改 PGPROC_1->waitLock = LOCK_B(记录当前进程到底被卡在哪个资源上)。
  4. 入列与断电:内核将 PGPROC_1 的指针塞入纯休眠队列 LOCK_B->waitProcs 的尾部。随后,调用操作系统原语 SemSleepPGPROC_1 释放 CPU 算力,陷入深度休眠。

【物理动作:PGPROC_2 申请 LOCK_A

  1. 同理,PGPROC_2LOCK_A->grantMask 处遭遇按位与运算冲突。
  2. 生成 PROCLOCK_2Agranted = false),挂入 LOCK_A->procLocks
  3. 关键指针修改:内核修改 PGPROC_2->waitLock = LOCK_A
  4. 入列与断电PGPROC_2 指针被塞入 LOCK_A->waitProcs 尾部,调用 SemSleep 陷入深度休眠。

【推导结论】

此时,物理闭环正式形成。两个业务进程全部处于 OS 级别的休眠态,CPU 占用率为 0。系统没有任何报错日志,处于绝对的物理死寂。


第三帧:时间复杂度的妥协与硬件中断(定时器触发)

【架构逻辑】

如果内核在每次产生 SemSleep 时都去遍历全库指针找闭环,图遍历算法 $O(V+E)$ 的时间复杂度会引发极高的 CPU 算力消耗。因此,内核采用惰性检测(Lazy Detection)

  1. 时钟倒计时:当 PGPROC_1PGPROC_2 进入休眠时,内核启动了由 deadlock_timeout 参数控制的硬件定时器(默认 1000ms)。
  2. 中断打断:1000ms 后,操作系统的时钟中断触发 SIGALRM 信号。
  3. 强制唤醒:该信号强行将 PGPROC_1(假设它先跨过 1 秒门槛)从休眠态拉起。
  4. 状态确认PGPROC_1 醒来后,核对自己的 PROCLOCK_1B->granted,发现依然是 false(说明不是因为拿到锁醒来的)。它立刻被内核控制流强制重定向,进入死锁检测核心函数:CheckDeadLock()

第四帧:内存指针的图论遍历(WFG 有向图的构建)

PGPROC_1 现在化身为侦探,开始在内存中顺着指针寻找闭环。为了保证遍历时数据结构不被其他进程修改,PGPROC_1 瞬间申请并持有了锁管理器的全局轻量级闩锁(LWLock)

  1. 起点查询PGPROC_1 读取自身的阻塞源:PGPROC_1->waitLock。指针指向了 LOCK_B
  2. 寻找持锁者(找边)PGPROC_1 遍历 LOCK_B->procLocks 链表,寻找其中 granted == true 的节点。命中 PROCLOCK_2B。顺着该契约的指针,找到了 PGPROC_2(图论确立:建立有向边 PGPROC_1 $\rightarrow$ PGPROC_2)
  3. 递归下潜PGPROC_1 读取 PGPROC_2 的阻塞源:PGPROC_2->waitLock。指针指向了 LOCK_A
  4. 寻找次级持锁者PGPROC_1 遍历 LOCK_A->procLocks 链表,寻找 granted == true 的节点。命中 PROCLOCK_1A。顺着该契约的指针,读取到了内存地址 —— 正是 PGPROC_1 自身的内存地址!(图论确立:建立有向边 PGPROC_2 $\rightarrow$ PGPROC_1)
  5. 闭环实锤:DFS(深度优先搜索)递归栈在比对内存地址时,发现起点与终点地址完全重合。一个完美的拓扑学环(Cycle)在 C 语言的指针链路中被绝对证明。

第五帧:内存结构的强行撕裂与系统恢复(物理斩杀)

在确认全是硬冲突(无法通过重排 waitProcs 队列顺序解开闭环)后,内核进入异常处理分支。

  1. 牺牲者选定与报错:通常触发检测的进程(即 PGPROC_1)被选为牺牲者。它向前端抛出 ERROR: deadlock detected
  2. 事务回滚(核心清理)PGPROC_1 的执行引擎触发 AbortTransaction()
    • 物理摘除:内核沿着 PGPROC_1->myProcLocks 链表,将 PROCLOCK_1BLOCK_B 的链表中 unlink(摘除),并将 PROCLOCK_1ALOCK_A 的链表中 unlink
    • 释放内存:调用 pfree() 销毁这两个契约结构体。LOCK_A 瞬间变为无主状态。
  3. 状态重算与精准唤醒
    • 内核重新计算 LOCK_A->grantMask(由于 PGPROC_1 的死亡,该掩码归零)。
    • 内核读取 LOCK_A->waitProcs 休眠队列的头部,提取出排在第一位的 PGPROC_2 的指针。
    • 内核调用系统底层指令 SemWakeup,向 PGPROC_2 的 CPU 核心发送通电信号。
  4. 闭环破除PGPROC_2 苏醒,顺利执行 grantMask 的位运算并拿到 LOCK_A。系统吞吐量恢复,死锁从物理层面上被彻底清除。

这就是一个死锁从合法获取、到指针死结、再到硬件中断拉起、最后 DFS 算法破环的纯工业级工程推演。


了解 www.876873.xyz 的更多信息

订阅后即可通过电子邮件收到最新文章。

postgresql数据库的 死锁,逻辑推导过程:等您坐沙发呢!

发表评论