postgresql regular lock常规锁申请与释放 以及fastpath快速申请优化的取舍

专栏内容
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表中删除;
    如果有请求者,也就是锁等待者,则需要唤醒;

遍历所有等待者,检查是否可以被唤醒,唤醒时,先授予锁,再唤醒,避够再次竞争;
等待者可以被唤醒的条件是:

  1. 等待者申请的锁模式与之前的等待者(没唤醒的),不会有锁冲突;
  2. 与已经持有锁者 不会产生锁冲突;
    如果产生冲突,都不会唤醒;
  • 清除本地锁记录并返回true;

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:[email protected]
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

猜你喜欢

转载自blog.csdn.net/senllang/article/details/131747255