postgresql源码学习(七)—— 自旋锁与轻量锁

67 篇文章 51 订阅
20 篇文章 0 订阅

一、 pg中的锁

pg中的锁可以分为3个层次:

  • 自旋锁(Spin Lock):是一种和硬件结合的互斥锁,借用了硬件提供的原子操作的原语来对一些共享变量进行封锁,通常适用于临界区比较小的情况。特点是:封锁时间很短、无死锁检测机制和等待队列、事务结束时不会自动释放SpinLock。
  • 轻量锁(Lightweight Lock):负责保护共享内存中的数据结构,有共享和排他两种模式,类似Oracle中的latch。特点是:封锁时间较短、无死锁检测机制、有等待队列、事务结束时会自动释放。
  • 常规锁(Regular Lock):就是通常说的对数据库对象的锁。按照锁粒度,可以分为表锁、页锁、行锁等;按照等级,pg锁一共有8个等级。特点是:封锁时间可以很长、有死锁检测机制和等待队列、事务结束时会自动释放。

二、自旋锁

       自旋锁顾名思义就是一直原地旋转等待的锁。一个进程如果想要访问临界区,必须先获得锁资源,如果不能获得,就会一直自旋,直到获取到锁资源。

       显然这种自旋会造成CPU浪费,但是通常它保护的临界区非常小,封锁时间很短,因此通常自旋比释放CPU资源带来的上下文切换消耗要小。

        如果机器拥有TAS(test-and-set)指令集,则pg会使用s_lock.h和s_lock.c中定义的实现机制。如果没有,则要用pg定义的信号量PGSemaphore来仿真SpinLock(在Spin.c文件中),显然后者效率不如前者。

        看了下源码介绍,自旋锁的实现太偏底层,并且有不少汇编语言,有兴趣请参考原书了。

三、 轻量锁

        如前所述,轻量锁负责保护共享内存中的数据结构,它正式的名字叫做Individual LWLocks。

1. 轻量锁类型

        pg中的轻量锁类型定义在lwlocknames.h文件中这个文件是在编译时由lwlocknames.txt生成的),pg 14中,目前有45种轻量锁。

#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)
…

      可以看出,Individual LWLocks 被保存在MainLWLockArray数组中(前48个值),每种Individual LWLocks都有自己固定要保护的对象,使用方式如下(shmem.c 文件ShmemInitStruct函数):

LWLockAcquire(ShmemIndexLock, LW_EXCLUSIVE);
LWLockRelease(ShmemIndexLock);

       每个builtin tranches代表一组LWLocks,这组LWLocks虽然封锁各自的内容,但它们的功能相同。以下定义在lwlock.h文件中

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 tranches对应的一部分被保存在MainLWLockArray数组中,另一部分在使用它们的结构体中。

/* 初始化NamedLWLockTrancheRequests个轻量锁 */
	for (int i = 0; i < NamedLWLockTrancheRequests; i++)
		LWLockRegisterTranche(NamedLWLockTrancheArray[i].trancheId,
							  NamedLWLockTrancheArray[i].trancheName);

2. 轻量锁定义

轻量锁的结构体定义如下:

typedef struct LWLock
{
	uint16		tranche;		/* tranche ID */
	pg_atomic_uint32 state;		/* state of exclusive/nonexclusive lockers,轻量锁的状态 */
	proclist_head waiters;		/* list of waiting PGPROCs,轻量锁的等待者列表 */
#ifdef LOCK_DEBUG
	pg_atomic_uint32 nwaiters;	/* 等待者数量 */
	struct PGPROC *owner;		/* last exclusive owner of the lock,最近一次排他锁的拥有者 */
#endif
} LWLock;

       State变量有32位,其中低24位作为共享锁的计数器,因此一个轻量锁最多可以有2^24个共享锁持锁者。有1位作为排他锁的标记,因为同一时间最多只能有一个持锁者。

3. 轻量锁的申请

假设一个新会话申请共享锁(如果持续有共享锁申请插队,排他锁申请者可能会饿死)

假设一个新会话申请排他锁

如果等待队列中第一个申请的是排他锁,则只有这一个申请者被唤醒,其他申请者继续等待。

如果等待队列中第一个申请的是共享锁,则所有共享锁申请者都被唤醒,其他排他锁申请者继续等待。

参考

《PostgreSQL技术内幕:事务处理深度探索》第2章

《PostgreSQL数据库内核分析》第7章

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值