专栏内容:
postgresql内核源码分析
手写数据库toadb
并发编程
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
==================================
定义
每种常规锁都需要定义几个要素,它由结构体 LockMethodData 定义;
typedef struct LockMethodData
{
int numLockModes;
const LOCKMASK *conflictTab;
const char *const *lockModeNames;
const bool *trace_flag;
} LockMethodData;
typedef const LockMethodData *LockMethod;
这几个要素分别是:
- 锁的模式类型,也就是锁分了几种加锁方式,比如这里表锁是8种,也就是8级表锁;
- 锁的冲突矩阵,它是一个按bit的二维表,也就是各级锁方式之间的冲突关系,比如读写互斥,读读不冲突等;
- 锁的名字,主要是为了查找调试;
postgresql 已经定义了一种默认锁 default_lockmethod, 也可以自定义用户锁
存储
regular lock是多进程间共享的,所以存储在共享内存中。
由这几个结构组织存储:
- LockMethodLockHash ,以locktag为hash存储使用的锁
- LockMethodProcLockHash , 存储锁的引用关系,由lock-proc对来存储,proc是每个backend信息
- FastPathStrongRelationLocks ,
- LockMethodLocalHash , 存储本进程持有的所,相同锁的话,只是引用计数递增
以上hash表,在初始化时就已经分配
申请
常规锁的申请主要在接口 LockAcquire 和 LockAcquireExtended中实现。
LockAcquireResult
LockAcquire(const LOCKTAG *locktag,
LOCKMODE lockmode,
bool sessionLock,
bool dontWait)
{
return LockAcquireExtended(locktag, lockmode, sessionLock, dontWait,
true, NULL);
}
LockAcquireResult
LockAcquireExtended(const LOCKTAG *locktag,
LOCKMODE lockmode,
bool sessionLock,
bool dontWait,
bool reportMemoryError,
LOCALLOCK **locallockp);
可以看到最终是在 LockAcquireExtended实现 ,前者只是简单调用关系;
申请流程
下面我们来看LockAcquireExtended中的实现流程
- 从本地锁记录中查找;如果找到则返回;如果没找到创建本地新记录;
- 在standby模式时特殊处理;只能获取只读锁,此时需要先获取事务ID,以便锁与事务关锁;
- fastpath 处理;
fastpath 只用在表锁模式下;当获取的锁小于4级ShareUpdateExclusiveLock时启用;
fastpath 可以记录FP_LOCK_SLOTS_PER_BACKEND 16个锁记录,根据locktag hash值取模,对应位置如果没有被占用count=0时,就进行fastpath;
通过 FastPathGrantRelationLock 进行获取锁 ;当对应bit位为0时,就直接获得锁,并将bit为置1,同时reloid数组中记录对应的reloid; 如果上次获取过4级以下的锁,那么也将直接获得;
这里有两个变量(proc)->fpLockBits和 (proc)->fpRelId[FP_LOCK_SLOTS_PER_BACKEND],前者记录锁模式,后者记录对应的reloid;前者是一个64位整型,每4bit为一组,可以记录16个锁;
- 创建或查找锁,并创建锁proclock
当申请的是表锁且锁级别大于4时,先检查与 fastpath锁的冲突; 先在FastPathStrongRelationLocks->count[fasthashcode]对应+1,占位; 然后通过ProcGlobal来遍历所有进程中的fastpath信息;如果发现有backend已经通过fastpath占有4级以下锁,那么就创建lock和lockproc,在对应的hash中加入,这样在后面锁冲突判断时就会发现;
- 检查锁冲突
从 LockMethodLockHash 查找锁的locktag,如果有说明已经有持有者;再从 LockMethodProcLockHash 创建持有者关系;
- 先检查与锁等待者的冲突情况,根据lock->waitMask 来检查冲突;如果有冲突,则进行锁排队等待;
- 再检查与已经持有的锁冲突情况,避免死锁等待; 这一步比较复杂,先检查持有锁,再检查锁组冲突;
经过以上两步,没有冲突,则获得锁;有冲突测进行锁排队等待;
- 锁冲突等待 ,至到有人唤醒为止
释放
在使用结束后释放锁,如果有等待者需要唤醒, 在LockRelease中进行处理。
bool
LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock);
主要流程如下:
- 检查本地是否记录锁的持有 LockMethodLocalHash ;
本地持有,那么先对锁持有计数 -1 ,如果不为零,那就释放完成,返回true;
如果锁计数为零时,先将本地持有锁数量-1,从资源管理中取消持有记录;
- 处理fastpath申请的情况
如果持的有为fastpath申请的锁,从fastpath信息中清除;并清除本地锁记录,返回true;
- 当然锁完全释放,从LockMethodLockHash和LockMethodProcLockHash中删除
- 检查当前是否持有锁,如果已经不持有,清除本地锁记录,返回false;
- 释放锁;
锁授予和已请求计数分别 -1; 如果当前锁模式授予为0时,将grantMask的锁模式位置0;
检查是否有等待者,也就是看waitMask是否有冲突,如果有则需要唤醒;
在proclock中将持有锁holdMask中将当前锁模式置为0;
- 清理锁并唤醒等待者;
如果当前不再持有锁,则将lockproc从hash表中删除;
如果当前锁的请求者为0时,将lock从hash表中删除;
如果有请求者,也就是锁等待者,则需要唤醒;遍历所有等待者,检查是否可以被唤醒,唤醒时,先授予锁,再唤醒,避够再次竞争;
等待者可以被唤醒的条件是:
- 等待者申请的锁模式与之前的等待者(没唤醒的),不会有锁冲突;
- 与已经持有锁者 不会产生锁冲突;
如果产生冲突,都不会唤醒;
- 清除本地锁记录并返回true;
结尾
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!