postgresql内核分析 spinlock与lwlock原理与实现机制

这篇具有很好参考价值的文章主要介绍了postgresql内核分析 spinlock与lwlock原理与实现机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

postgresql内核分析 spinlock与lwlock原理与实现机制,#  postgresql内核源码分析,postgresql,postgresql,数据库,linux,database,c语言,架构,分布式

专栏内容
postgresql内核源码分析
手写数据库toadb
并发编程
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

========================================

概述

在postgresql 中,有大量的并发同步,所以避免不了使用很多保护锁。
同时为了提升并发的性能,针对不同场景下的加锁需求,设计了:

  • spinlock 自旋锁
  • lightweight lock(LWLocks) 轻量级锁
  • regular lock(a/k/a heavyweight locks) 普通锁
  • SIReadLock predicate locks 谓词锁

本文主要针对这四种锁进行分享,起抛砖引玉的作用。

spinlock

是一种持有时间非常短的锁。它是通过test and set 原子操作来实现。

通过一定时间内的检测,如果没有持有就获得,这个时间大概是1min,超时就会导致ERR错误。
所以此类锁,都是一些状态保护,很快就释放,中间没有IO,大的内存操作。

它的实现依赖于操作系统的原子操作实现,所以通过宏定义共公接口,底层根据不同操作系统实现不同。

也可以说是一种无锁化的实现,需要原子操作TAS和内存同步。

操作函数

#define SpinLockInit(lock)	S_INIT_LOCK(lock)

#define SpinLockAcquire(lock) S_LOCK(lock)

#define SpinLockRelease(lock) S_UNLOCK(lock)

#define SpinLockFree(lock)	S_LOCK_FREE(lock)

底层操作函数有这四个,是通过宏定义给出,对于不同操作系统下,定义了具体的原子操作。
在支持TAS 原语的操作系统上,用TAS来实现,如加锁的函数如下

int s_lock(volatile slock_t *lock, const char *file, int line, const char *func)
{
	SpinDelayStatus delayStatus;

	init_spin_delay(&delayStatus, file, line, func);

	while (TAS_SPIN(lock))
	{
		perform_spin_delay(&delayStatus);
	}

	finish_spin_delay(&delayStatus);

	return delayStatus.delays;
}

#define TAS_SPIN(lock)    (*(lock) ? 1 : TAS(lock))

static __inline__ int
tas(volatile slock_t *lock)
{
	slock_t		_res = 1;

	__asm__ __volatile__(
		"	lock			\n"
		"	xchgb	%0,%1	\n"
:		"+q"(_res), "+m"(*lock)
:		/* no inputs */
:		"memory", "cc");
	return (int) _res;
}

可以看到核心代码是通过汇编实现TAS操作,大致流程是这样:

  1. 检测lock是否为0 ,如果不为0,说明还没有解锁,继续等,直到超时;
  2. 如果已经解锁,就走入汇编代码;锁定总线,通过xchgb 原子交换lock和_res=1 两个值,进行内存同步;加锁成功;
  3. 此时TAS_PIN返回0,等待结束;

而slock_t 是什么类型呢?
如果在支持TAS指令的操作系统下是如下定义

typedef unsigned char slock_t;

是一个字节,这样可以很快的检测和原子交换赋值

注意事项

通过上面的原理介绍,可以看到它等待的时间非常短,这就是说在锁持有时,不能占用太久时间。

因此,在持有spinlock时,只是一些状态的获取和赋值,就要立即释放,否则就会有大量超时。
在锁持有此间,避免磁盘,网络,函数调用等其它额外操作。

轻量级锁 lightweight lock

介绍

轻量级锁将加锁过程分成了两个阶段,第一阶段通过原子操作来检测,如果可以加锁,就加锁成功;如果不能加锁,进入第二阶段,将自己加入等待队列,并阻塞在信号量上;

主要用于共享内存和数据块的操作保护

它因为分了两个阶段,所以较一般的系统级锁性能更高效一些。
它提供了如下特点:

  • 能够快速检测锁状态,并且获取到锁;
  • 每个后台进程只能有一个排队中的轻量级锁;
  • 在持有锁期间,信号会被阻塞
  • 在错误时会释放锁;

数据结构

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;	/* number of waiters */
	struct PGPROC *owner;		/* last exclusive owner of the lock */
#endif
} LWLock;

extern bool LWLockAcquire(LWLock *lock, LWLockMode mode);
extern bool LWLockConditionalAcquire(LWLock *lock, LWLockMode mode);
extern bool LWLockAcquireOrWait(LWLock *lock, LWLockMode mode);
extern void LWLockRelease(LWLock *lock);

初始化

加锁

  • 判断是否已经持有锁数量,超过上限;阻塞信号中断;
  • 第一阶段 尝试加锁,加上时直接返回锁;否则将自己放入等待队列;再次尝试加锁;
  • 第二阶段 如果仍没有获取到锁时,在当前backend对应的 MyProc中的信号量上进行等待;

直到被唤醒,如果proc->lwWaiting == LW_WS_NOT_WAITING时,继续等待;

  • 当获取到锁时,将锁加入自己持有锁的数组中记录;

解锁

从本等数据中获取当前锁的加锁模式; 从锁中解除;
如果有等待者,将它们从等待队列中移除,然后唤醒它们;等待者们将再次竞争;

等待锁释放

bool
LWLockAcquireOrWait(LWLock *lock, LWLockMode mode);
  • 介绍

这个接口有点意思,即可以获取锁,也用来等待别人释放锁;

当前锁如果没有被占用,则占有锁后函数返回;
如果当前锁被占用,则等待锁,等别人释放锁后,就直接返回,而不持有锁。

  • 用途

这个函数主要用来在写WAL时,获取锁,因为同时只能有一个进程写WAL;
如果当前没有人写WAL,则持有锁后,执行WAL写入。
如果当前已经有人持有锁,在写WAL,那么自己的WAL也会被写入,因为WAL是顺序写入,后写时,需要把前面的内容都要写入。

条件变量

static bool LWLockConflictsWithVar(LWLock *lock,
					   uint64 *valptr, uint64 oldval, uint64 *newval,
					   bool *result)
bool LWLockWaitForVar(LWLock *lock, uint64 *valptr, uint64 oldval, uint64 *newval);
void LWLockUpdateVar(LWLock *lock, uint64 *valptr, uint64 val);

基于轻量级锁,又实现了一组类似于条件变量的接口;

LWLockWaitForVar检测变量是否变化,如果没人持有锁,那就直接返回;如果有锁,则等待,直到锁释放后,返回新值;
LWLockUpdateVar是改变变量的值,并通知等待者,唤醒等待者;

锁排队

lightweiht lock可能会长时间等待,因此每个backend只能有一个正在等待的轻量级锁,所以每个backend都会有一个信号量;

struct PGPROC
{
	// other members ... 
	PGSemaphore sem;			/* ONE semaphore to sleep on */
	// other members ... 
};

信号量定义在PROC结构上,当进入信号量等待时,同时也会把自己的MyProc添加到 lock->waiters 列表成员中。

在锁持有者释放锁时,会删除队列中的所有成员,同时唤醒等待者的信号量;

在介绍了排队和释放后,就会发现它存在两个问题:

  • 等锁的饿死问题
  • 惊群问题

当然lwlock 队列的唤醒也是顺序唤醒,同时加锁分为两阶段,这就在一定程度上避免了上述问题。

另外lwlock加锁是非常频,可能在很短时间有加锁/释放,所以需要更简洁直接的加锁方式。

结尾

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

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!文章来源地址https://www.toymoban.com/news/detail-537244.html

到了这里,关于postgresql内核分析 spinlock与lwlock原理与实现机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • postgresql15 内核源码分析-buffer查找接口

        专栏内容:postgresql内核源码分析 个人主页:我的主页 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. 目录 前言 概述 接口介绍 调用场景分析 详细分解 结尾 本文是基于postgresql 15的代码进行分析解读,演示是在centos8系统上进行。   在postgresql中,SQL引擎

    2023年04月09日
    浏览(55)
  • 【Linux 内核源码分析】RCU机制

    Linux内核的RCU(Read-Copy-Update)机制是一种用于实现高效读取和并发更新数据结构的同步机制。它在保证读操作不被阻塞的同时,也能够保证数据的一致性。 RCU的核心思想是通过延迟资源释放来实现无锁读取,并且避免了传统锁带来的争用和开销。具体而言,RCU维护了一个“回

    2024年01月15日
    浏览(72)
  • postgresql 内核源码分析 btree索引插入分析,索引页面分裂流程,多举措进行并发优化,对异常进行保护处理

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 ​ 开源贡献 : toadb开源库 个人主页 :我的主页 管理社区 :开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. B树索引在PostgreSQL中得到了广泛应用,它是一种自平衡树数据结构,可以维

    2024年02月08日
    浏览(62)
  • Linux内核源码分析 (6)RCU机制及内存优化屏障

    问题: RCU 英文全称为 Read-Copy-Update ,顾名思义就是 读-拷贝-更新 ,是 Linux 内核中重要的同步机制。 Linux 内核已有原子操作、读写信号量等锁机制,为什么要单独设计一个比较复杂的新机制? RCU的原理 RCU记录所有指向共享数据的指针的使用者,当要修改该共享数据时,首先

    2024年02月10日
    浏览(55)
  • postgresql 内核源码分析 表锁relation lock的使用,session lock会话锁的应用场景,操作表不再困难

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 个人主页 :我的主页 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. ================================================================== 当表打开,或者操作表时,都需要对表relation 进行加锁,表锁定义了8种级互

    2024年02月16日
    浏览(50)
  • 【postgresql 基础入门】表的约束(二) 唯一unique、非空not null、check、exclusion约束,原理机制以及多列组合约束

    ​ 专栏内容 : postgresql内核源码分析 手写数据库toadb 并发编程 个人主页 :我的主页 管理社区 :开源数据库 座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物. 在数据库中,数据类型可以限制数据存储的大小,也能在一定程度上限制存储的数据种类,但是对于数

    2024年04月08日
    浏览(56)
  • 《Linux内核源码分析》(2)进程原理及系统调用

    操作系统的作用 :作为硬件的使用层,提供使用硬件资源的能力, 进程的作用 :作为操作系统使用层,提供使用操作系统抽象出的资源层的能力 进程、线程和程序的区别 :进程指计算机中已运行的程序。进程本身不是基本的运行单位,而是线程的容器。 程序本身只是指令

    2024年02月07日
    浏览(47)
  • Spark重温笔记(三):Spark在企业中为什么能这么强?——持久化、Checkpoint机制、共享变量与内核调度原理全攻略“

    前言:今天是温习 Spark 的第 3 天啦!主要梳理了 Spark 核心数据结构:RDD(弹性分布式数据集),包括RDD持久化,checkpoint机制,spark两种共享变量以及spark内核调度原理,希望对大家有帮助! Tips:\\\"分享是快乐的源泉💧,在我的博客里,不仅有知识的海洋🌊,还有满满的正能量

    2024年04月09日
    浏览(46)
  • Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制

    Linux源码解读系列是一套深入剖析Linux内核源码的教程,旨在帮助读者理解Linux操作系统的底层原理和工作机制。该系列教程从Linux内核的各个模块入手,逐一分析其源码实现,并结合实际应用场景进行讲解。通过学习本系列,读者可以深入了解Linux操作系统的底层机制,掌握

    2024年01月21日
    浏览(47)
  • 【计算机网络】TCP原理 | 可靠性机制分析(四)

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】 本专栏旨在分享学习计算机网络的一点学习心得,欢迎大家在评论区交流讨论💌 接收方在接收到数据后并不立即发送ACK报文,而是等待一定的延迟时间,

    2024年01月16日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包