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

news2024/10/2 6:43:13

本篇将着重讲解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;
			}
			else
				return 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.coloradmin.cn/o/369936.html

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

相关文章

Helm安装Harbor

一、介绍 1.1 Harbor Harbor 是由 VMware 公司为企业用户设计的 Registry Server 开源项目&#xff0c;包括了权限管理 (RBAC)、LDAP、审计、管理界面、自我注册、HA 等企业必需的功能&#xff0c;同时针对中国用户的特点&#xff0c;设计镜像复制和中文支持等功能。目前该项…

说说Java“锁“ 事

文章目录前言大厂面试题复盘 —— 并发编程高级面试解析从轻松的乐观锁和悲观锁开讲通过8种情况演示锁运行案例&#xff0c;看看我们到底锁的是什么公平锁和非公平锁可重入锁(又名递归锁)死锁及排查写锁(独占锁)/读锁(共享锁)自旋锁SpinLock无锁 -> 独占锁 -> 读写锁 -&g…

五种IO模型以及select多路转接IO模型

目录 一、典型IO模型 1.1 阻塞IO 1.2 非阻塞IO 1.3 信号驱动I0 1.4 IO多路转接 1.5 异步IO 多路转接的作用和意义 二、多路转接IO模型&#xff08;select&#xff09; 2.1 接口 2.2 接口当中的事件集合&#xff1a; fd_set 2.2 select使用事件集合&#xff08;位图&am…

ip公司和soc公司是什么?

IP 公司和 SoC 公司都是半导体行业的重要组成部分&#xff0c;但它们的角色和职责略有不同。IP&#xff08;Intellectual Property&#xff09;公司主要提供可重用的知识产权组件&#xff0c;也称为 IP 核或 IP 模块&#xff0c;这些组件可以在设计芯片的过程中被集成到芯片中。…

Git代码冲突-不同分支之间的代码冲突

1、解决思路在团队开发中&#xff0c;提交代码到Git仓库时经常会遇到代码冲突的问题。- 原因&#xff1a;多人对相同的文件进行了编辑&#xff0c;造成代码存在差异化- 解决方案&#xff1a;1. 使用工具或git命令对比不同分支代码的差异化2. 把不同分支中有效代码进行保留&…

[译文] 基于PostGIS3.1 生成格网数据

根据格网进行数据统计与分析是一种常用的方法&#xff0c;相比自然地理边界与行政管理边界而言&#xff0c;使用格网有如下特点&#xff1a;每个格网之间地位相等&#xff0c;没有上下级之分。每个格网的面积都相等。相邻两个格网单元中心点之间距离相等。适用于将数据从“空间…

ThreeJS加载公路GeoJson数据实现流光效果

threejs加载公路geojson数据,跟加载行政区域的原理一样,唯一不同的是geojson格式不一样,路线并不是连贯起来的,按照路段进行的拆分,在加载的时候问题不大,正常解析然后转墨卡托投影,但是在做流光效果时,需要对geojson进行重新组合. 实现效果:

Android:反编译apk踩坑/apktool/dex2jar/JDGUI

需求描述 想要反编译apk文件&#xff0c;搜到了这篇博客&#xff1a;Android APK反编译就这么简单 详解&#xff08;附图&#xff09;&#xff0c;非常有参考价值~但其中的工具下载链接都已404&#xff0c;而本杂鱼实际操作的过程中也出现了亿点点点点点点的问题&#xff0c;于…

电子技术——反馈对放大器极点的影响

电子技术——反馈对放大器极点的影响 放大器的频率响应和稳定性可以直接由其极点决定。因此我们将深入反馈对放大器极点的影响。 稳定性和极点位置 我们首先讨论稳定性和极点位置的关系。首先我们给出结论&#xff0c;对于任何一个稳定的放大器&#xff0c;其极点都处在 sss …

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

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

Elasticsearch7.8.0版本进阶——段合并

目录一、段的概述1.1、段的概念1.2、段的缺点1.3、如何解决段数量暴增问题二、段合并的流程三、段合并的注意事项一、段的概述 1.1、段的概念 每一 段 本身都是一个倒排索引。 1.2、段的缺点 由于自动刷新流程每秒会创建一个新的段 &#xff0c;这样会导致短时间内的段数量…

interrupt多线程设计模式

1. 两阶段终止-interrupt Two Phase Termination 在一个线程T1中如何“优雅”终止线程T2&#xff1f;这里的【优雅】指的是给T2一个料理后事的机会。 错误思路 ● 使用线程对象的stop()方法停止线程&#xff08;强制杀死&#xff09; —— stop&#xff08;&#xff09;方法…

Linux内核的虚拟内存(MMU、页表结构)

前言&#xff1a;内存是程序得以运行的重要物质基础。如何在有限的内存空间运行较大的应用程序&#xff0c;曾是困扰人们的一个难题。为解决这个问题&#xff0c;人们设计了许多的方案&#xff0c;其中最成功的当属虚拟内存技术。Linux作为一个以通用为目的的现代大型操作系统&…

【git】Idea中git的使用

配置git 创建git仓库 不同颜色代表的含义 红色——未加入版本控制&#xff1b;绿色——已经加入控制暂未提交&#xff1b;蓝色——加入&#xff0c;已提交&#xff0c;有改动&#xff1b;白色——加入&#xff0c;已提交&#xff0c;无改动&#xff1b;灰色——版本控制已忽略文…

8、STM32 FSMC驱动LCD(ILI93xx)

本文使用FSMC驱动LCD显示&#xff0c;关于建议先看之前的7、STM32 FSMC驱动SRAM一文 硬件连接&#xff1a; 一、CubeMx配置FSMC驱动LCD ILI93xx 此章只为快速使用LCD&#xff0c;不涉及原理、指令说明 显示屏驱动文件参考正点探索者 1、CubeMx图形配置 此处的时序还可以调…

GLOG如何清理日志

1 日志清理 其实GLOG很长时间以来都没有日志清理功能。小白对此也很震惊&#xff0c;还特意去查了GLOG的提交记录。代码的提交记录显示&#xff0c;GLOG与日志清理有关的最初代码是2019年11月1日&#xff0c;而这个开源项目的起始时间可以追溯到2008年。也就是说&#xff0c;在…

浅谈liunx init.d 和 rc.local 两种起动方式

浅谈liunx init.d 和 rc.local 两种起动方式 以rabbitmq 举例 &#xff08;一&#xff09;.init.d 方式 开机自动重启设置 1.在/etc/init.d 目录下新建一个 rabbitmq [rootlocalhost init.d]# vi rabbitmq具体脚本如下所示&#xff1a; #!/bin/bash # # chkconfig: 2345 …

【离线数仓-7-数据仓库开发DIM层设计要点-拉链表同步装载脚本】

离线数仓-7-数据仓库开发DIM层设计要点-拉链表同步&装载脚本离线数仓-7-数据仓库开发DIM层设计要点-拉链表同步&装载脚本一、DIM层 维度模型 设计要点6.用户维度表 -拉链表1.用户维度表 前期梳理2.用户维度表 DDL表设计分析3.用户维度表 加载数据分析1.拉链表首日装载数…

RocketMQ 5.x新版本部署优化一览

​ RocketMQ从2022年9月份开始推出了新的5.x大版本。相比于之前的4.x版本&#xff0c;5.x版本向云原生前进了一大步。在增强原因功能的基础上&#xff0c;更是支持多语言客户端&#xff0c;周边生态也进行了补强和完善&#xff0c;明显可以看到离Kafka老大哥又近了很大一步。 …

linux网络编程-多进程实现TCP并发服务器

服务端流程步骤socket函数创建监听套接字lfdbind函数将监听套接字绑定ip和端口listen函数设置服务器为被动监听状态&#xff0c;同时创建一条未完成连接队列&#xff08;没走完tcp三次握手流程的连接&#xff09;&#xff0c;和一条已完成连接队列&#xff08;已完成tcp三次握手…