postgres 源码解析51 LWLock轻量锁--2

news/2024/3/29 16:53:24/文章来源:https://blog.csdn.net/qq_52668274/article/details/129170343

本篇将着重讲解LWLock涉及的主要API工作流程与实现原理,相关基础知识见回顾:postgres 源码解析50 LWLock轻量锁–1

API介绍

函数API功能
CreateLWLocks分配LWLocks所需的内存并进行初始化
LWLockNewTrancheId分配新的Tranche ID,供用户使用Extension模块中的LWLocks
LWLockAcquire获取指定的LWLock
LWLockConditionalAcquire获取LWLock,与LWLockAcquire不同的是未获取到不等待直接返回
LWLockAttemptLock尝试获取LWLock
LWLockRelease释放已申请的LWLock
LWLockWakeup唤醒等待队列中LWLock

LWLock的主要操作

1 LWLock的空间分配 (CreateLWLocks

该函数为LWLocks分配内存空间并初始化,同时注册扩展NamedTranche。
执行流程:
1) 首先计算出LWLocks和NamedTranche所占用的内存空间,该过程定义在LWLockShmemSize函数中;
2)定义并初始化全局LWLocks数组首地址MainLWLockArray;
3) 调用 InitializeLWLocks函数初始化所有LWLocks, 主要是设置lock->state和lock->tranched_id字段信息;
4) 注册扩展LWLocks,即建立Tranche ID与Tranche Name之间的联系。

/** Allocate shmem space for the main LWLock array and all tranches and* initialize it.  We also register extension LWLock tranches here.*/
void
CreateLWLocks(void)
{StaticAssertStmt(LW_VAL_EXCLUSIVE > (uint32) MAX_BACKENDS,"MAX_BACKENDS too big for lwlock.c");StaticAssertStmt(sizeof(LWLock) <= LWLOCK_PADDED_SIZE,"Miscalculated LWLock padding");if (!IsUnderPostmaster){Size		spaceLocks = LWLockShmemSize();int		   *LWLockCounter;char	   *ptr;/* Allocate space */ptr = (char *) ShmemAlloc(spaceLocks);/* Leave room for dynamic allocation of tranches */ptr += sizeof(int);/* Ensure desired alignment of LWLock array */ptr += LWLOCK_PADDED_SIZE - ((uintptr_t) ptr) % LWLOCK_PADDED_SIZE;MainLWLockArray = (LWLockPadded *) ptr;/** Initialize the dynamic-allocation counter for tranches, which is* stored just before the first LWLock.*/LWLockCounter = (int *) ((char *) MainLWLockArray - sizeof(int));*LWLockCounter = LWTRANCHE_FIRST_USER_DEFINED;/* Initialize all LWLocks */InitializeLWLocks();}/* Register named extension LWLock tranches in the current process. */for (int i = 0; i < NamedLWLockTrancheRequests; i++)LWLockRegisterTranche(NamedLWLockTrancheArray[i].trancheId,NamedLWLockTrancheArray[i].trancheName);
}
2 LWLock Tranche ID的分配 (LWLockNewTrancheId

该函数实现了LWLock的分配,目的是向用户提供使用Extension模块中的轻量锁。
1 首先从共享内存中获取动态分配计数器LWLockCounter的地址;
2 申请spin_t自旋锁,该锁用于保护LWLock的分配过程;
3 自增分配计数器的值,释放锁;
4 最终返回该值作为新分配LWLock的全局标识Tranche ID;

/** Allocate a new tranche ID.*/
int
LWLockNewTrancheId(void)
{int			result;int		   *LWLockCounter;LWLockCounter = (int *) ((char *) MainLWLockArray - sizeof(int));SpinLockAcquire(ShmemLock);result = (*LWLockCounter)++;SpinLockRelease(ShmemLock);return result;
}
3 LWLock锁的获取(LWLockAcquire

1 安全性检查,pg规定每个进程最多持有200个轻量锁,若超过报错退出;
2 开中断,调用LWLockAttemptLock函数尝试获取轻量锁(第一次),该函数返回值有两种,一种是该锁处于非空闲状态导致未获取到,则需要等待,另一种是成功获取到锁无需等待;
3 根据2结果进行后续处理。如果不等待,则表明成功获取锁跳出循环,反之进入步骤4;
4 调用LWLockQueueSelf函数将该进程加入到锁的等待队列;
5 再次调用LWLockAttemptLock(第二次)函数尝试获取轻量锁,如果无需等待,则成功获取锁,并将自身进程从锁的等待队列中移除跳出循环;反之在锁队列中等待直至被其他进程唤醒,回到步骤2。
6 更新本进程持有的锁数目和锁模式。

注:从源代码可以看出,有两次相邻的LWLockAttemptLock调用,推测其原因为LWLock锁保护的临界区较小,锁的持有时间较短,可能在将自身加入锁等待队列期间,其他进程已释放该锁,在一定程度上能够减小等待时间(一种优化)。
在这里插入图片描述

4 LWLockConditionalAcquire

轻量锁获取的另一种接口函数为LWLockConditionalAcquire,该函数与LWLockAcquire的不同点在于获取不到锁时不等待直接返回。

5 LWLockAttemptLock

该函数的功能是尝试获取LWLock,其本质是通过一系列原子操作来实现指定模式下锁的获取。
1 首先调用 pg_atomic_read_u32读取该锁的状态信息old_state;
2 进入死循环
1)根据指定的锁模式判断锁是否处于空闲lock_free并更新desired_state(该变量初始值=old_state,更新操作是加上LW_VAL_EXCLUSIVE/LW_VAL_SHRAED);
2)如果成功调用pg_atomic_compare_exchange_u32函数实现上述old_stat与desired_state交换,则进一步根据lock_free标识判断:若lock_free = true, 则标记获取该LWLock并结束;反之,获取不到该锁并结束;
3)若未成功调用pg_atomic_compare_exchange_u32函数,则表明该锁已被其他人获取,需要再次循环重试。

/** Internal function that tries to atomically acquire the lwlock in the passed* in mode.** This function will not block waiting for a lock to become free - that's the* callers job.** Returns true if the lock isn't free and we need to wait.*/
static bool
LWLockAttemptLock(LWLock *lock, LWLockMode mode)
{uint32		old_state;AssertArg(mode == LW_EXCLUSIVE || mode == LW_SHARED);/** Read once outside the loop, later iterations will get the newer value* via compare & exchange.*/old_state = pg_atomic_read_u32(&lock->state);/* loop until we've determined whether we could acquire the lock or not */while (true){uint32		desired_state;bool		lock_free;desired_state = old_state;if (mode == LW_EXCLUSIVE){lock_free = (old_state & LW_LOCK_MASK) == 0;if (lock_free)desired_state += LW_VAL_EXCLUSIVE;}else{lock_free = (old_state & LW_VAL_EXCLUSIVE) == 0;if (lock_free)desired_state += LW_VAL_SHARED;}/** Attempt to swap in the state we are expecting. If we didn't see* lock to be free, that's just the old value. If we saw it as free,* we'll attempt to mark it acquired. The reason that we always swap* in the value is that this doubles as a memory barrier. We could try* to be smarter and only swap in values if we saw the lock as free,* but benchmark haven't shown it as beneficial so far.** Retry if the value changed since we last looked at it.*/if (pg_atomic_compare_exchange_u32(&lock->state,&old_state, desired_state)){if (lock_free){/* Great! Got the lock. */if (mode == LW_EXCLUSIVE)lock->owner = MyProc;return false;}elsereturn true;	/* somebody else has the lock */}}pg_unreachable();
}
LWLock锁的释放 (LWLockRelease

LWLock锁的释放定义在LWLockRelease函数中,实现流程如:
1 首先检查该LWLock是否持有,如果不持有则报错退出;
2 将锁从进程自身的持锁队列中移除,更新持锁队列;
3 根据锁模式,调用pg_atomic_sub_fetch_u32函数清除锁标记表明锁释放;
4 如果有其他进程等待该锁,则需根据某种调度策略唤醒等待进程;

LWLockWakeup

该函数负责将锁等待队列中进程唤醒,其唤醒规则为;
1 如果等待队列中的第一个申请者申请的是排他锁,则只有该进程被唤醒,其他进程仍处于等待状态;
2 如果等待队列中的第一个申请者申请的是共享锁,那么所有申请共享锁的进程都可以被唤醒,只留下申请排他锁的进程;
在这里插入图片描述
唤醒策略示意图
在这里插入图片描述

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

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

相关文章

结构效度分析流程

结构效度分析流程如下图 一、结构效度的意义 效度分析在学术研究中非常常见&#xff0c;结构效度是为了分析“从量表获得的结果与设计该量表时所假定的理论之间的符合程度”。简单来讲&#xff0c;在研究者设计量表之初&#xff0c;一般会预设好几个维度&#xff0c;在经过因子…

kafka入门到精通

文章目录一、kafka概述&#xff1f;1.定义1.2消息队列1.2.1 传统消息队列的使用场景1.2.2 消息队列好处1.2.3 消息队列两种模式1.3 kafka基础架构二、kafka快速入门1.1使用docker-compose安装kafka1.2测试访问kafka-manager1.3 查看kafka版本号1.4 查看zookeeper版本号1.5 扩展…

python学习之OpenCV-Python模块的部分应用示例(生成素描图和动漫图)

文章目录前言一、图片转灰度二、对图片进行二值化处理三、对图片去除噪点四、调整图片透明度五、生成素描滤镜效果图&#xff08;方法结合应用&#xff09;六、生成动漫卡通滤镜效果图&#xff08;方法结合应用&#xff09;总结前言 OpenCV 是一个图像和视频处理库&#xff0c…

掌握饮食健康:了解你的宏量营养素摄入

谷禾健康 // 俗话说“病从口入”&#xff0c;我们的健康状况很大一部分取决于饮食。而食物基本上是由各种营养素构成的。 宏量营养素是人体大量需要的必需营养成分。宏量营养素指的是“三大”营养素&#xff1a;蛋白质、脂肪和碳水化合物&#xff0c;它们是我们饮食中的关键。 …

【JavaScript】基本语法大全

前言&#xff1a; 大家好&#xff0c;我是程序猿爱打拳。在学习C和Java这样的后端编程语言后&#xff0c;我们大概率会学习一些关于前端的语言如HTMLJavaScript。又因为前后端基本语法有些许不同&#xff0c;因此我整理出来。今天给大家讲解的是JS中的数据类型、运算符、选择结…

【华为OD机试模拟题】用 C++ 实现 - 最低位排序(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 货币单位换算(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 选座位(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 停车场最大距离(2023.Q1) 【华为OD机试模拟题】用 C++ 实现 - 重组字符串(2023.Q1) 【华为OD机试模…

Eth-trunk :LACP模式链路聚合实战

Eth-trunk : LACP模式链路聚合实战 需求描述 PC1和PC3数据vlan10 &#xff0c;网段为192.168.10.0 /24PC2和PC4数据vlan20 &#xff0c;网段为192.168.20.0 /24确保设备之间互联互通&#xff0c;使用最大互联带宽并没有环路确保相同网段的PC可以互通判断交换机之间的每个端口…

ros下用kinectv2运行orbslam2

目录 前提 创建工作空间 orbslam2源码配置、测试&#xff1a; 配置usb_cam ROS功能包 配置kinect 前提 vim 、 cmake 、 git 、 gcc 、 g 这些一般都装了 主要是Pangolin 、 OpenCV 、 Eigen的安装 18.04建议Pangolin0.5 创建工作空间 我们在主目录下创建一个catkin_…

Node 10.0.8.6:9003 is unknown to cluster

解决方案解决方案一解决方案一 ① 概念介绍 公网ip&#xff1a;就是任意两台连接了互联网的电脑可以互相ping ip,能够通的ip 内网ip&#xff1a;只是在内网中使用无法与外网连接的ip ②问题背景 在腾讯云上搭建的一个redis集群&#xff0c;集群启动后 可以看到启动节点…

TX Text Control .NET Server for ASP.NET 31.0 SP2 CRK

用于 ASP.NET 31.0 SP2 的 TX 文本控件 .NET 服务器 用于 ASP.NET 的 TX 文本控件 .NET 服务器 TX Text Control Server for ASP.NET 是用于 Web 应用程序或服务的服务器端组件。它是一个完全可编程的 ASP.NET 文字处理器引擎&#xff0c;提供了广泛的文字处理功能。使用 TX Te…

C++中的内存管理

文章目录前言1.C中内存空间的划分2.C内存管理方式1.对内置类型的处理2.对自定义类型的处理3.new和delete实现原理4.定位new3.总结1. malloc/free和new/delete的区别2. 内存泄漏前言 C中的内存空间划分和C语言是很像的&#xff0c;基本上区别不大。但是因C中&#xff0c;引入了…

davis2016评估教程

DAVIS 2016是VOS任务中的一个经典的benchmark&#xff0c;但是一些VOT的算法有时候也可以预测mask&#xff0c;所以也会在上面测一测性能&#xff0c;本次就随手记录一下自己评测的过程&#xff0c;有需要的小伙伴可以往下看。 DAVIS 2016数据集官方项目网站&#xff1a;https:…

TCP四次挥手

TCP 四次挥手过程是怎样的&#xff1f; TCP 断开连接是通过四次挥手方式。 双方都可以主动断开连接&#xff0c;断开连接后主机中的「资源」将被释放&#xff0c;四次挥手的过程如下图&#xff1a; 客户端打算关闭连接&#xff0c;此时会发送一个 TCP 首部 FIN 标志位被置为 1…

node报错

记录bug:运行 npx -p storybook/cli sb init 时报错gyp info spawn C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exegyp info spawn args [gyp info spawn args build/binding.sln,gyp info spawn args /nologo,gyp info spawn args…

prometheus + alterManager + 飞书通知,实现服务宕机监控告警;实测可用

架构设计图 最终效果图 项目准备 xml依赖 <!-- 监控相关 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>io.…

消息队列--Kafka

Kafka简介集群部署配置Kafka测试Kafka1.Kafka简介 数据缓冲队列。同时提高了可扩展性。具有峰值处理能力&#xff0c;使用消息队列能够使关键组件顶住突发的访问压力&#xff0c;而不会因为突发的超负荷的请求而完全崩溃。 Kafka是一个分布式、支持分区的&#xff08;partition…

JAVA 8 新特性 Lamdba表达式

Java8 新特性&#xff1a; 1、Lamdba表达式 2、函数式接口 3、方法引用和构造引用 4、Stream API 5、接口中的默认方法和静态方法 6、新时间日期API 7、Optional 8、其他特性 Java8 优势&#xff1a;速度快、代码更少&#xff08;增加了新的语法 Lambda 表达式&#xff09;、强…

Android 架构 MVC MVP MVVM,这一波你应该了然于心

MVC&#xff0c;MVP和MVVM是软件比较常用的三种软件架构&#xff0c;这三种架构的目的都是分离&#xff0c;避免将过多的逻辑全部堆积在一个类中。在Android中&#xff0c;Activity中既有UI的相关处理逻辑&#xff0c;又有数据获取逻辑&#xff0c;从而导致Activity逻辑复杂不单…

Wireshark抓包

Wireshark 1 抓包时间显示格式 2 界面显示列设置 3 protocol协议解析 4 过滤器 tcp.port&#xff1a;TCP端口tcp.dstport&#xff1a;TCP目的端口tcp.srcport&#xff1a;TCP源端口udp.port&#xff1a;UDP端口udp.dstport&#xff1a;UDP目的端口udp.srcport&#xff1a;UDP…

月薪过3W的软件测试工程师,都是怎么做到的?

对任何职业而言&#xff0c;薪资始终都会是众多追求的重要部分。前几年的软件测试行业还是一个风口&#xff0c;随着不断地转行人员以及毕业的大学生疯狂地涌入软件测试行业&#xff0c;目前软件测试行业“缺口”已经基本饱和。当然&#xff0c;我说的是最基础的功能测试的岗位…