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

专栏内容
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_DEBUGpg_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
如有错误或者疏漏欢迎指出,互相学习。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/9848.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬件速攻-激光测距传感器VL530L0X

介绍 VL53L0X是一种时间飞行(Time-of-Flight,TOF)技术的激光测距传感器芯片。TOF技术利用红外激光发射器发送短脉冲光束,并通过测量光束从传感器到目标物体返回的时间来计算距离。 外观 现象 串口打印数据 接线 VCC 3.3V G…

MFC 单文档模式

Doc类利用自带框架存数据 void CCADDoc::Serialize(CArchive& ar) {if (ar.IsStoring()){// TODO: 在此添加存储代码//保存数据到文件ar << m_nShapeCount;for (int i 0; i < m_arrShapes.GetSize(); i){CShape* pShape NULL;pShape (CShape*)m_arrShapes[i];…

Prometheus 时序数据

一 时序索引 Prometheus 存储的是时序数据&#xff0c;时间戳&#xff08;timestamp&#xff09;来源于服务端本地的系 统时间。Prometheus 使用 Unix 时间戳&#xff08;即自 1970 年 1 月 1 日 00:00:00 UTC 起经过的秒数&#xff09;表示时间。 数 据 格 式 &#xff1a; …

C++—异常与类型转换、大小端存储、不使用额外空间的情况下交换两个数

异常 常见的异常包括&#xff1a;数组下标越界&#xff0c;除法计算的时候除数为0&#xff0c;动态分配空间时空间不足。 try&#xff0c;throw&#xff0c;catch #include <iostream> using namespace std; int main() {double m 1, n 0;try {cout << "b…

基于 RK3399+fpga 的 VME 总线控制器设计(一)总体设计

2.1 需求分析及技术指标 2.1.1 需求分析 VME 总线控制器需要实现数据传输、中断处理、测量显示等功能。同时还需 要具有操作系统、底层驱动程序以及功能接口等&#xff0c;以方便用户进行上层应用软件开 发及使用。 本课题需要实现 VME 控制器的国产化开发&#xff0…

FPGA实验六:PWM信号调制器设计

目录 一、实验目的 二、设计要求 三、实验代码 1.顶层文件代码 2.仿真文件部分代码 3.系统工程文件 四、实验结果及分析 1、引脚锁定 2、仿真波形及分析 3、下载测试结果及分析 五、实验心得 一、实验目的 &#xff08;1&#xff09;掌握通信信号调制过程及实现原理…

【模式识别目标检测】——模式识别技术车牌检测应用

目录 引入 一、模式识别主要方法 1、统计模式识别 2、基于隐马尔可夫模型识别 3、模糊模式识别 4、人工神经网络模式识别 总结 二、模式识别应用 1、车牌定位 2、车牌识别 参考文献&#xff1a; 引入 人在观察事物或现象时&#xff0c;常寻找它与其他事物或现象不同…

嵌入式Linux开发实操(二):uboot+kernal

要理解如何进行嵌入式Linux编程,必须知道系统启动引导过程: 上电后,芯片将开始执行其启动固件,它就是uboot,主要目的是加载一个程序,然后在芯片上运行它,uboot通过查看引导模式寄存器、保险丝Fuses或GPIO引脚的状态来确定从哪里加载程序,比如从从eMMC flash启动。 SPL是…

【MySQL体系结构及CetOS7安装MySQL和修改密码】

MySQL体系结构及安装MySQL MySQL体系结构CentOS7安装MySQL四种方法1、离线安装2、在线安装3、通用二级制方式4、容器方式安装 设置及修改密码忘记密码恢复 MySQL体系结构 MySQL是一种常用的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;其体系结构包括以下&…

更改VS code Jupyter 插件的默认快捷键

更改vscode 中Jupyter插件的默认快捷键&#xff0c;解放插入空行的系统快捷键 替换Jupyter默认快捷键 更改vscode 中Jupyter插件的默认快捷键&#xff0c;解放插入空行的系统快捷键打开keyboard shortcuts 设置方法一方法二 更换快捷键 end Jupyter 插件很好的在VS code中集成了…

华为OD机试真题B卷 Python 实现【整理扑克牌】,附详细解题思路

目录 一、题目描述步骤1步骤2步骤3 二、输入描述三、输出描述四、解题思路五、Python算法源码六、效果展示1、输入2、输出3、说明 一、题目描述 给定一组数字&#xff0c;表示扑克牌的牌面数字&#xff0c;忽略扑克牌的花色&#xff0c;请按如下规则对这一组扑克牌进行整理&am…

系统移植 kernel 移植 配置网卡IP NFS网络文件系统移植 7.4

给fs4412移植内核 Kernel&#xff08;内核&#xff09;是操作系统的核心组件&#xff0c;它负责管理计算机硬件和软件资源&#xff0c;提供对底层硬件的访问和控制&#xff0c;以及为应用程序提供运行环境。内核是操作系统的最底层&#xff0c;负责处理系统的各种任务和功能&a…