【PGCCC】Postgresql LWLock 原理

前言

postgresql 的架构是基于多进程的,进程之间往往存在着数据共享,比如缓存读写。这时 postgresql 需要提高一种锁机制来支持并发访问,但是 linux 只实现了线程间的读写锁,所以 postgresql 自己实现进程间的读写锁,它主要利用了信号量和自旋锁来实现。

进程结构

每个进程都对应一个独立 PGPROC 实例,它包含了多个成员,下面只列出与锁相关的

struct PGPROC 
{
    BackendId	backendId;		/* This backend's backend ID (if assigned) */
    PGSemaphore sem;			// 信号量

    // 正在等待的lwLock
    bool		lwWaiting;		// 是否在等待lwlock
    uint8		lwWaitMode;		// lwlock加锁类型,有读锁和写锁
    proclist_node lwWaitLink;	// 位于等待列表中的位置
}

PGPROC 实例存储在进程共享内存里,在创建进程时会被初始化。这里需要提醒下,信号量 sem 的初始值为0,属于无名信号量。

锁结构

LWLock结构体表示读写锁,结构如下:

typedef struct LWLock
{
	uint16		tranche;		// tranche id,表示用于哪个方面
	pg_atomic_uint32 state;		// 状态值,包含多个标记位
	proclist_head waiters;		/* 等待进程链表 */
} LWLock;

需要介绍下 state 字段的标记位

#define LW_VAL_SHARED         1                    // 1~23位表示读锁的数量
#define LW_VAL_EXCLUSIVE     ((uint32) 1 << 24)    // 是否写锁已经被占用
#define LW_FLAG_LOCKED       ((uint32) 1 << 28)    // 锁标记位,用来保护进程列表的并发操作
#define LW_FLAG_HAS_WAITERS  ((uint32) 1 << 30)    // 是否有进程在等待,用于快速判断等待列表是否为空
#define LW_FLAG_RELEASE_OK   ((uint32) 1 << 29)    // 是否可以执行唤醒操作(目前我还没弄懂为什么需要这个标记位)

加锁原理

LWLockAcquire函数负责整个加过程,这里将过程分为下面四步:

  1. 尝试获取锁,如果获取成功,会直接返回,否则执行第二步。
  2. 将自身进程添加到等待队列后,还会进行一次尝试获取锁,因为有可能在刚加入队列之前,锁恰好被释放。如果获取锁成功,则将自身从队列删除并且直接返回,否则执行第三步。
  3. 通过信号量阻塞,等待其他进程唤醒。
  4. 当因为锁释放被唤醒之后(该进程已经被唤醒进程从等待队列里删除了),会回到第一步

从上面可以看出来,这里采用的是非公平锁机制,也就是谁的速度快,谁就能获取到锁,没有先来先获取的顺序。

尝试获取锁

static bool LWLockAttemptLock(LWLock *lock, LWLockMode mode);

尝试通过 CAS 操作,设置LW_VAL_EXCLUSIVE或LW_VAL_SHAR

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值