8-pg内核之锁管理器(三)轻量锁

概念

pg数据库中,存在有大量的共享内存,进程并发访问这些共享内存的时候,就需要加锁保护,所以提供了轻量锁来提供对共享内存访问的保护。
早期版本的轻量锁是由自旋锁实现的,但是,对于频繁以共享模式获取的锁,自旋锁的开销过高,特别是在尝试获取实际上为空闲的共享锁时,会出现不必要的自旋等待。当前版本做了优化,针对未被独占锁定的锁提供无等待的共享锁获取,并利用原子操作来获取和释放锁。
轻量锁提供两种锁模式:共享模式和排他模式,其相容性矩阵如下:

锁模式LW_SHAREDLW_EXCLUSIVE
LW_SHARED×
LW_EXCLUSIVE××
对于数据库的开发者而言,轻量锁有两种使用方法:统一保存的Individual LWLocks和Builtin Tranches

Individual LWLocks

Individual LWLocks被保存在MainLWLockArray数组中,该数组的前48个都是Individual LWLocks,每个LWLock都是全局唯一的,每种Individual LWLocks都有自己固定要保护的对象,它的使用方式是:

LWLockAcquire
xxxx
LWLockRelease

Individual LWLocks保存了48种锁,保存在文件lwlocknames.txt中

#define ShmemIndexLock (&MainLWLockArray[1].lock)
#define OidGenLock (&MainLWLockArray[2].lock)
#define XidGenLock (&MainLWLockArray[3].lock)
#define ProcArrayLock (&MainLWLockArray[4].lock)
#define SInvalReadLock (&MainLWLockArray[5].lock)
#define SInvalWriteLock (&MainLWLockArray[6].lock)
#define WALBufMappingLock (&MainLWLockArray[7].lock)
#define WALWriteLock (&MainLWLockArray[8].lock)
#define ControlFileLock (&MainLWLockArray[9].lock)
#define XactSLRULock (&MainLWLockArray[11].lock)
#define SubtransSLRULock (&MainLWLockArray[12].lock)
#define MultiXactGenLock (&MainLWLockArray[13].lock)
#define MultiXactOffsetSLRULock (&MainLWLockArray[14].lock)
#define MultiXactMemberSLRULock (&MainLWLockArray[15].lock)
#define RelCacheInitLock (&MainLWLockArray[16].lock)
#define CheckpointerCommLock (&MainLWLockArray[17].lock)
#define TwoPhaseStateLock (&MainLWLockArray[18].lock)
#define TablespaceCreateLock (&MainLWLockArray[19].lock)
#define BtreeVacuumLock (&MainLWLockArray[20].lock)
#define AddinShmemInitLock (&MainLWLockArray[21].lock)
#define AutovacuumLock (&MainLWLockArray[22].lock)
#define AutovacuumScheduleLock (&MainLWLockArray[23].lock)
#define SyncScanLock (&MainLWLockArray[24].lock)
#define RelationMappingLock (&MainLWLockArray[25].lock)
#define NotifySLRULock (&MainLWLockArray[26].lock)
#define NotifyQueueLock (&MainLWLockArray[27].lock)
#define SerializableXactHashLock (&MainLWLockArray[28].lock)
#define SerializableFinishedListLock (&MainLWLockArray[29].lock)
#define SerializablePredicateListLock (&MainLWLockArray[30].lock)
#define SerialSLRULock (&MainLWLockArray[31].lock)
#define SyncRepLock (&MainLWLockArray[32].lock)
#define BackgroundWorkerLock (&MainLWLockArray[33].lock)
#define DynamicSharedMemoryControlLock (&MainLWLockArray[34].lock)
#define AutoFileLock (&MainLWLockArray[35].lock)
#define ReplicationSlotAllocationLock (&MainLWLockArray[36].lock)
#define ReplicationSlotControlLock (&MainLWLockArray[37].lock)
#define CommitTsSLRULock (&MainLWLockArray[38].lock)
#define CommitTsLock (&MainLWLockArray[39].lock)
#define ReplicationOriginLock (&MainLWLockArray[40].lock)
#define MultiXactTruncationLock (&MainLWLockArray[41].lock)
#define OldSnapshotTimeMapLock (&MainLWLockArray[42].lock)
#define LogicalRepWorkerLock (&MainLWLockArray[43].lock)
#define XactTruncationLock (&MainLWLockArray[44].lock)
#define WrapLimitsVacuumLock (&MainLWLockArray[46].lock)
#define NotifyQueueTailLock (&MainLWLockArray[47].lock)

#define NUM_INDIVIDUAL_LWLOCKS		48

Builtin Tranche

每个Builtin Tranche可能对应多个LWLock,它代表的是一组LWLocks,这组LWLocks虽然各自封各自的内容,但是他们的功能是相同的。
Builtin Tranche包含的类型如下:

typedef enum BuiltinTrancheIds
{
	LWTRANCHE_XACT_BUFFER = NUM_INDIVIDUAL_LWLOCKS,
	LWTRANCHE_COMMITTS_BUFFER,
	LWTRANCHE_SUBTRANS_BUFFER,
	LWTRANCHE_MULTIXACTOFFSET_BUFFER,
	LWTRANCHE_MULTIXACTMEMBER_BUFFER,
	LWTRANCHE_NOTIFY_BUFFER,
	LWTRANCHE_SERIAL_BUFFER,
	LWTRANCHE_WAL_INSERT,
	LWTRANCHE_BUFFER_CONTENT,
	LWTRANCHE_REPLICATION_ORIGIN_STATE,
	LWTRANCHE_REPLICATION_SLOT_IO,
	LWTRANCHE_LOCK_FASTPATH,
	LWTRANCHE_BUFFER_MAPPING,
	LWTRANCHE_LOCK_MANAGER,
	LWTRANCHE_PREDICATE_LOCK_MANAGER,
	LWTRANCHE_PARALLEL_HASH_JOIN,
	LWTRANCHE_PARALLEL_QUERY_DSA,
	LWTRANCHE_PER_SESSION_DSA,
	LWTRANCHE_PER_SESSION_RECORD_TYPE,
	LWTRANCHE_PER_SESSION_RECORD_TYPMOD,
	LWTRANCHE_SHARED_TUPLESTORE,
	LWTRANCHE_SHARED_TIDBITMAP,
	LWTRANCHE_PARALLEL_APPEND,
	LWTRANCHE_PER_XACT_PREDICATE_LIST,
	LWTRANCHE_FIRST_USER_DEFINED
}			BuiltinTrancheIds;

这些Builtin Tranche对应的锁一部分被保存在MainLWLockArray数组中,如LWTRANCHE_BUFFER_MAPPING,LWTRANCHE_LOCK_MANAGER,LWTRANCHE_PREDICATE_LOCK_MANAGER等。另一部分则是保存在使用他们的结构体中,如WALInsert的锁,是在WALInsertLockPadded结构体中定义,并在XLogShemInit函数中初始化。

	for (i = 0; i < NUM_XLOGINSERT_LOCKS; i++)//初始化每个锁的内容
	{
		LWLockInitialize(&WALInsertLocks[i].l.lock, LWTRANCHE_WAL_INSERT);
		WALInsertLocks[i].l.insertingAt = InvalidXLogRecPtr;
		WALInsertLocks[i].l.lastImportantAt = InvalidXLogRecPtr;
	}

无论是Builtin Tranche还是Individual LWLocks,他们都被保存在共享内存中,只是保存的位置和方式略有不同。

Extension中使用轻量锁

提供了两种方法

  1. 通过RequestNamedLWLockTranche和GetNamedLWLockTranche函数实现。

  2. 通过LWLockNewTrancheId函数获得新的Tranche ID,然后将Tranche ID和Tranche Name通过LWLockRegisterTranche建立联系,然后由LWLockInitialize来初始化轻量锁。

结构

LWLock

typedef struct LWLock
{
	uint16		tranche;		/* 唯一标识符*/
	pg_atomic_uint32 state;		/* 原子变量,用来保存轻量锁的状态 */
	proclist_head waiters;		/*  等待队列 */
#ifdef LOCK_DEBUG
	pg_atomic_uint32 nwaiters;	/* 等待者数量*/
	struct PGPROC *owner;		/* 锁的owner*/
#endif
} LWLock;

原子变量state是一个32位的值,用来记录当前的锁的状态。其中,

  • 低24位用来作为共享锁的计数区,因为共享锁之间并不冲突,所以可以同时持有,一把轻量锁最多有2^24个持锁者
  • 第24位是标记位,用来标记是否存在排他锁,因为排它锁与其他锁时互斥的,所以1位即可。
  • 第28位用来标记是否有人持锁。
  • 第29位用来标记是否存在重复release的情况
  • 第30位用来标记是否有等待者。
    ![[Pasted image 20240511102658.png]]
#define LW_FLAG_HAS_WAITERS			((uint32) 1 << 30)//轻量锁是否有等待者
#define LW_FLAG_RELEASE_OK			((uint32) 1 << 29)//轻量锁是否重复释放
#define LW_FLAG_LOCKED				((uint32) 1 << 28)//是否有人持有该轻量锁

#define LW_VAL_EXCLUSIVE			((uint32) 1 << 24)//是否有人持有排他锁
#define LW_VAL_SHARED				1

#define LW_LOCK_MASK				((uint32) ((1 << 25)-1))//持锁情况掩码
/* Must be greater than MAX_BACKENDS - which is 2^23-1, so we're fine. */
#define LW_SHARED_MASK				((uint32) ((1 << 24)-1))//共享锁掩码,2^23-1

MainLWLockArray

//128
#define LWLOCK_PADDED_SIZE	PG_CACHE_LINE_SIZE
//填充一整个缓存线
typedef union LWLockPadded
{
	LWLock		lock;
	char		pad[LWLOCK_PADDED_SIZE];
} LWLockPadded;

extern PGDLLIMPORT LWLockPadded *MainLWLockArray;

NamedLWLockTranche

typedef struct NamedLWLockTranche
{
	int			trancheId;
	char	   *trancheName;
} NamedLWLockTranche;

函数

在这里插入图片描述

CreateLWLocks

在共享内存中分配空间,其结构大致如下:
在这里插入图片描述

InitializeLWLocks

分配空间后,根据轻量锁的类型初始化轻量锁。

  • 初始化NUM_INDIVIDUAL_LWLOCKS
  • 初始化BUFFER_MAPPING_LWLOCK_OFFSET,Buffer中用的锁
  • 初始化LWTRANCHE_LOCK_MANAGER,lmgr中常用的锁
  • 初始化LWTRANCHE_PREDICATE_LOCK_MANAGER,predicate lmgrs的轻量锁
  • 初始化NamedLWLockTrancheArray,Extension自定义的轻量锁

LWLockAcquire

申请一把特定模式的轻量锁

  • while循环中尝试获取轻量锁,如果获取成功,退出while循环直接调到最后一步;如果没有获取成功,继续下一步
		mustwait = LWLockAttemptLock(lock, mode);//尝试获取锁
		if (!mustwait)//获取到锁
		{
			LOG_LWDEBUG("LWLockAcquire", lock, "immediately acquired lock");
			break;				/* got the lock */
		}
  • 将自己添加到等待队列中,但是添加后并不会立即睡眠,因为在加入等待队列的过程中,锁可能已经被释放。
LWLockQueueSelf(lock, mode);//把我们加到等待队列中
  • 再次尝试获取轻量锁,这次如果获取成功,则将自己从等待队列中去掉,然后退出while循环直接调到最后一步。
		mustwait = LWLockAttemptLock(lock, mode); //再次尝试获取一次锁
		if (!mustwait)
		{
			LWLockDequeueSelf(lock);//获取到锁后,再把自己从等待队列中去掉
			break;
		}
  • 到这里说明两次尝试获取锁都失败了,自己也已经添加到等待队列,现在需要睡眠了。
		for (;;)
		{
			PGSemaphoreLock(proc->sem);//睡眠
			if (!proc->lwWaiting)
				break;
			extraWaits++;
		}

		/* Retrying, allow LWLockRelease to release waiters again. */
		pg_atomic_fetch_or_u32(&lock->state, LW_FLAG_RELEASE_OK);//再次尝试获取锁
  • 等待自己被唤醒,被唤醒后,重复上面操作,继续尝试获取锁,知道获取到锁退出。
  • 获取到锁,更新相关计数,修正进程等待信号量的计数,以解决任何已接收的唤醒事件
	held_lwlocks[num_held_lwlocks].lock = lock;
	held_lwlocks[num_held_lwlocks++].mode = mode;
	while (extraWaits-- > 0)
		PGSemaphoreUnlock(proc->sem);//把当前进程等待时额外增加的计数减去

LWLockRelease

释放轻量锁

LWLockConditionalAcquire

尝试获取一把轻量锁,并立即返回申请结果,如果获取不到也不会睡眠。

	bool		mustwait;
	HOLD_INTERRUPTS();//禁止中断
	mustwait = LWLockAttemptLock(lock, mode);//尝试申请锁
	if (mustwait)
	{
		RESUME_INTERRUPTS();//没申请成功,释放中断
	}
	else//申请成功,更新计数
	{
		held_lwlocks[num_held_lwlocks].lock = lock;
		held_lwlocks[num_held_lwlocks++].mode = mode;
	}
	return !mustwait;

LWLockConflictsWithVar

等待原子变量更新

LWLockWaitForVar

等待锁释放或者原子变量更新

RequestNamedLWLockTranche

在共享内存中申请额外的轻量锁,只适用于Extension中。

GetNamedLWLockTranche

根据传入的tranche_name返回对应的轻量锁的地址

【参考】

  1. 《PostgreSQL数据库内核分析》
  2. 《Postgresql技术内幕-事务处理深度探索》
  3. 《PostgreSQL指南:内幕探索》
  4. pg14源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值