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.mzph.cn/news/1475.shtml

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

相关文章

260道网络安全工程师面试题汇总(附答题解析+配套资料)

由于我之前写了不少网络安全技术相关的文章和回答,不少读者朋友知道我是从事网络安全相关的工作,于是经常有人私信问我: 我刚入门网络安全,该怎么学? 想找网络安全工作,应该要怎么进行技术面试准备&…

Oracle删除约束条件不会锁表

最近有个需求要删除一个Oracle约束条件,但是由于不知道会不会锁表,所以测试了一下 使用python写了段代码验证下 import cx_Oracle conn cx_Oracle.connect(dba_li/oracle192.168.56.105:1521/orcl) #用自己的实际数据库用户名、密码、主机ip地址 替…

通过rebase,解决gitlab提示的pipeline failed

之前提交的MQ,提示Pipeline failed: gitlab提交MQ提示Pipeline failed的解决办法_pipeline:failed_柳鲲鹏的博客-CSDN博客 又报错,给的提示: 本以为万事大吉。结果发现自己的库也提示有问题。按照上文的办法修改之后还是不对。…

Java设计模式-责任链(Chain of Responsibility)模式

介绍 Java责任链(Chain of Responsibility)设计模式是指很多处理对象构成一个链,链中前一个对象指向后一个对象。请求在链中传递,一个请求可以被一个或者多个对象处理。调用方(即客户端)不知道请求会被链中…

【C++】C++11 (3): lambda表达式和包装器

一、lambda表达式 C98中的一个例子 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。 #include <algorithm> #include <functional> int main() {int a[] { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较&#xff…

JS、Vue鼠标拖拽

JS代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevic…

每日打卡day8——差分练习

输入一个长度为 n 的整数序列。 接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c&#xff0c;表示将序列中 [l,r] 之间的每个数加上 c。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数 n 和 m。 第二行包含 n 个整数&#xff0c;表示整数序列…

Go和Java实现观察者模式

Go和Java实现观察者模式 在监控系统中&#xff0c;我们需要采集监控指标的信息&#xff0c;假设当采集的指标信息超过阈值时我们需要对该监控指标持久化到 数据库中并且进行告警。 本文通过指标采集持久化和告警来说明观察者模式的使用&#xff0c;使用Go语言和Java语言实现…

使用Linux Deploy搭建服务器(五)Linux Deploy添加自启动(亲测可用)linuxdeploy自动化配置

添加开机自动任务,可以省去开机后手动输入初始化命令的操作 一、方式1 run-parts方式,也就是rc.local的方式(这种方式有时候不生效,按照4中的配置就好了) 1、Linux Deploy配置 1.点击右下角的设置图标进入设置界面 2.划到“初始化”那块,“启用”打上勾 选项“run-p…

文件包含漏洞

一、什么是文件包含漏洞 1.文件包含漏洞概述 和SQL注入等攻击方式一样&#xff0c;文件包含漏洞也是一种注入型漏洞&#xff0c;其本质就是输入一段用户能够控制的脚本或者代码&#xff0c;并让服务端执行。 什么叫包含呢&#xff1f;以PHP为例&#xff0c;我们常常把可重复使…

基于GIS的生态敏感性评价与产业路径选择研究:以江西省吉安市为例

导读: 确立绿水青山就是金山银山的理念,建立生态经济体系,是新时代生态环境保护与经济发展的协调之道。对产业规划而言,与生态同行,构建绿色产业体系,是推动地区高质量发展的根本要求。鉴于此,文章从实证角度出发,以江西省吉安市为研究对象,采用生态敏感性评价方法,选…

【Python基础】- break和continue语句

在Python中&#xff0c;break和continue是用于控制循环语句的特殊关键字。 break语句用于跳出当前的循环&#xff08;for循环或while循环&#xff09;&#xff0c;并继续执行紧接着的循环外的代码。它通常用于满足某个条件时提前结束循环。例如&#xff0c;考虑以下示例&#…

RabbitMQ 同样的操作一次成功一次失败

RabbitMQ 是一个功能强大的消息队列系统&#xff0c;广泛应用于分布式系统中。然而&#xff0c;我遇到这样的情况&#xff1a;执行同样的操作&#xff0c;一次成功&#xff0c;一次失败。在本篇博文中&#xff0c;我将探讨这个问题的原因&#xff0c;并提供解决方法。 我是在表…

深入理解阻塞与非阻塞、同步与异步

0、前言 阻塞与非阻塞、同步与异步&#xff0c;这几个概念虽然每次看都能看的懂&#xff0c;但是过一段时间又会忘掉&#xff0c;所以系统的整理一下他们的区别&#xff0c;也方便自己后面忘掉的时候快速记忆&#xff0c;虽然网上有很多的说明这些概念的帖子&#xff0c;但是每…

Django dumpdata 迁移数据库数据

Django 迁移数据库数据 数据导出 连接上数据源数据库&#xff0c;执行 dumpdata 导出数据。 python3 manage.py dumpdata main --indent 4 -o mydata.json数据导入 连接上目标数据库&#xff0c;比如通过在 local_settings.py 配置本地数据库&#xff0c;执行 loaddata 导入…

西安丨高时空分辨率、高精度一体化预测技术之风、光、水能源自动化预测技术应用

目录 ​第一章 预测平台讲解及安装 第二章 一体化预测工具详解与数据获取及制备 第三章 风资源预测自动化技术 第四章 太阳能资源自动化预测技术 第五章 水资源自动化预测技术 第六章 后处理自动化技术 更多推荐 能源是国民经济发展和人民生活必须的重要物质基础。在过去…

Linux5.17 Ceph应用

文章目录 计算机系统5G云计算第四章 LINUX Ceph应用一、创建 CephFS 文件系统 MDS 接口1.服务端操作2.客户端操作 二、创建 Ceph 块存储系统 RBD 接口三、创建 Ceph 对象存储系统 RGW 接口四、OSD 故障模拟与恢复 计算机系统 5G云计算 第四章 LINUX Ceph应用 一、创建 CephF…

微服务: 04-springboot中rabbitmq配置,消息回收,序列化方式

目录 1. 本文简介: 1.1 java序列化的缺点 ---> 1.1.1 无法跨语言 --->1.1.2 易被攻击 ---> 1.1.3 序列化后的流太大 ---> 1.1.4 序列化性能太差 2. 配置总览 2.1 基础配置 2.2 连接重试配置 2.3 异常重试机制 2.4 确认模式(本篇是自动) ---> 2.4.1…

linux文件系统只读导致监听异常

项目经理发来截图&#xff0c;监听无法启动了&#xff0c;截图如下 orcl:/home/oraclehydb> lsnrctl start LSNRCTL for Linux: Version 11.2.0.4.0 - Production on 18-JUL-2023 11:29:54 Copyright (c) 1991, 2013, Oracle. All rights reserved. Starting /u01/app/…

QML 入门

QML 入门 Qt 基本模块Qt Quick 开发所需基本技术QML 基本语法QML 数据类型基本数据类型&#xff08;39&#xff09;boolcolor 颜色类型coordinate 坐标类型date 日期时间类型doubleenumeration 枚举类型font 字体类型geocircle 几何圆数据类型geopath 几何路径数据类型geopolyg…