轻量级锁实现1——结构体解析、初始化

news2025/3/1 17:41:11

瀚高数据库
目录
环境
文档用途
详细信息

环境
系统平台:Linux x86-64 Red Hat Enterprise Linux 7
版本:14
文档用途
从底层理解轻量级锁的实现,从保护共享内存的角度理解轻量级锁的使用场景,包括上锁、等待、释放,理解轻量级锁的互斥(execlusive)和共享(shared)2种状态。

详细信息
1.轻量级锁空间分配
锁作为并发场景下使用的一种进程间的同步机制,因此有必要将其放在共享内存中。

/*

 * Set up shared memory and semaphores.

 *

 * Note: if using SysV shmem and/or semas, each postmaster startup will

 * normally choose the same IPC keys.  This helps ensure that we will

 * clean up dead IPC objects if the postmaster crashes and is restarted.

 */

CreateSharedMemoryAndSemaphores();

image.png

以下代码是轻量级锁的初始化代码:LWLock系统通常由一个主要的LWLock数组组成,每个数组元素对应一个具体的锁资源。而"tranche" 是指将LWLock数组进一步分割成更小的单元,以提供更细粒度的并发控制。

/*

 * Allocate shmem space for the main LWLock array and all tranches and

 * initialize it.  We also register extension LWLock tranches here.

 */



void CreateLWLocks(void)

{

	if (!IsUnderPostmaster) // 主进程或者是standlone进程

	{

		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);

}

1.1计算轻量级锁数组占用的共享内存的存储空间的大小:

/*

 * Compute shmem space needed for LWLocks and named tranches.

 */

Size LWLockShmemSize(void)

{

	Size		size;

	int			i;

     //NUM_FIXED_LWLLOCKS是pg内核使用的锁的数量,写死的数量

	int			numLocks = NUM_FIXED_LWLOCKS;

     //加上其它模块比如动态库请求的锁的数量,例如pg_stat_statements在请求共享内存的钩子函数的代码

     

/*

     static void

     pgss_shmem_request(void)

     {

	if (prev_shmem_request_hook)

		prev_shmem_request_hook();



	RequestAddinShmemSpace(pgss_memsize());

	RequestNamedLWLockTranche("pg_stat_statements", 1);

     }

*/

	/* Calculate total number of locks needed in the main array. */

	numLocks += NumLWLocksForNamedTranches();



	/* Space for the LWLock array. */

	size = mul_size(numLocks, sizeof(LWLockPadded)); // 1.2



	/* Space for dynamic allocation counter, plus room for alignment. */

	size = add_size(size, sizeof(int) + LWLOCK_PADDED_SIZE);



	/* space for named tranches. */

	size = add_size(size, mul_size(NamedLWLockTrancheRequests, sizeof(NamedLWLockTranche)));



	/* space for name of each tranche. */

	for (i = 0; i < NamedLWLockTrancheRequests; i++)

		size = add_size(size, strlen(NamedLWLockTrancheRequestArray[i].tranche_name) + 1);



	return size;

}

1.2 LWLockPadded结构体解析

/*

 * In most cases, it's desirable to force each tranche of LWLocks to be aligned

 * on a cache line boundary and make the array stride a power of 2.  This saves

 * a few cycles in indexing, but more importantly ensures that individual

 * LWLocks don't cross cache line boundaries.  This reduces cache contention

 * problems, especially on AMD Opterons.  In some cases, it's useful to add

 * even more padding so that each LWLock takes up an entire cache line; this is

 * useful, for example, in the main LWLock array, where the overall number of

 * locks is small but some are heavily contended.

 */

通俗一点说,为什么要设计一个LWLockPadded的union?

因为union成员共享存储空间,所以整个union占用的空间大小将为LWLOCK_PADDED_SIZE的大小(这个已经断言过了)。

LWLOCK_PADDED_SIZE的大小是cpu一级缓存的大小,所以结果将是LWLock独占一个缓存行,也就是CPU一次读取的单位大小。

这么做的好处是提高性能:CPU使用分布式一致性缓存协议MESI(当然实际情况不止这四种状态),当CPU1要改动LWLock,那么对其它的CPU会发送read invalidate消息,其它CPU如果有LWLock在其缓存行,将会清空。如果CPU0下次要修改LWLock,同样也会对其它CPU发送read invalidate消息。但是如果缓存行保存了非LWLock内容,那么对非LWLock内容的修改操作,将会产生invalidate影响(false sharing).因此将其扩充到1个缓存行大小,这样避免其它变量对该LWLock的影响。

#define LWLOCK_PADDED_SIZE	PG_CACHE_LINE_SIZE



StaticAssertDecl(sizeof(LWLock) <= LWLOCK_PADDED_SIZE,

				 "Miscalculated LWLock padding");



/* LWLock, padded to a full cache line size */

typedef union LWLockPadded

{

	LWLock		lock;

	char		pad[LWLOCK_PADDED_SIZE];

} LWLockPadded;

举例说明:读取物理核心数,将每个线程绑定一个物理cpu,本机只有cpu0~cpu3,然后对atomics进行单字节的修改,随着线程增多,每个线程的执行时间会随时间增长,因为cpu重复清空(invalidate)和加载(read -> modified)。如果是根据业务将数据分在不同的cache line,那么效率会有提升。

#define _GNU_SOURCE

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <pthread.h>

#include <stdatomic.h>

#include <time.h>

#include <assert.h>

#include <sched.h>



#define CL_SIZE 64 



pthread_barrier_t barrier;

pthread_mutex_t sync_mutex;

atomic_long nsSum;

int sync_count;

struct CacheLine {

   atomic_char atomics[CL_SIZE];

};





// this function used to get cache line size from runtime

/*

 * unsigned int get_cache_line_size_x86() {

    unsigned int cache_line_size = 0;

    

    // Execute CPUID instruction with EAX=0x80000006 to get cache information

    __asm__ volatile (

        "mov $0x80000006, %%eax\n\t"

        "cpuid\n\t"

        "mov %%ecx, %0\n\t"

        : "=r" (cache_line_size)  // Output: store cache line size in variable

        :                         // No input

        : "%eax", "%ebx", "%ecx", "%edx"  // Clobbered registers

    );



    // Extract cache line size from bit 0 to 7 of ECX register

    cache_line_size = cache_line_size & 0xFF;



    return cache_line_size;

}

*/



// thread function 

void* threadFunc(void* arg) {

    struct CacheLine* cacheLine = (struct CacheLine*)arg;

    pthread_barrier_wait(&barrier);



    pthread_mutex_lock(&sync_mutex);

    // .....

    pthread_mutex_unlock(&sync_mutex);



    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC, &start);

    for (size_t r = 10000000LL; r > 0; --r) {

        atomic_fetch_add_explicit(&cacheLine->atomics[r % CL_SIZE], 1, memory_order_relaxed);

    }

    clock_gettime(CLOCK_MONOTONIC, &end);



    int64_t diff = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);

    atomic_fetch_add_explicit(&nsSum, diff, memory_order_relaxed);



    return NULL;

}





int main() {

    int hc = sysconf(_SC_NPROCESSORS_ONLN);

    cpu_set_t cs;

    // loops just for counter

    for (int nThreads = 1; nThreads <= hc; ++nThreads)

        {

        pthread_t threads[nThreads];

        struct CacheLine cacheLine;

        nsSum = 0;



        pthread_barrier_init(&barrier, NULL, nThreads);

        pthread_mutex_init(&sync_mutex, NULL);

        sync_count = nThreads;

   // loops for threads number

        for (int t = 0; t < nThreads; ++t) {

            pthread_create(&threads[t], NULL, threadFunc, &cacheLine);

            CPU_ZERO(&cs);

            CPU_SET(t, &cs);

            assert(pthread_setaffinity_np(threads[t], sizeof(cs), &cs) == 0); 

        }



        for (int t = 0; t < nThreads; ++t) {

            pthread_join(threads[t], NULL);

        }



        pthread_barrier_destroy(&barrier);

        pthread_mutex_destroy(&sync_mutex);

        printf("%d: %ld\n", nThreads, (long)(nsSum/(1.0e7 * nThreads) + 0.5));

    }



    return 0;

}

结果:

image.png
在这里插入图片描述

1.3 轻量级锁在共享内存中的布局
lwlock.jpg

2.轻量级锁的初始化
对于上图的空间顺序进行初始化,主要看下LWLockInitialize的实现:

/*

 * Initialize LWLocks that are fixed and those belonging to named tranches.

 */

static void

InitializeLWLocks(void)

{

	int			numNamedLocks = NumLWLocksForNamedTranches();

	int			id;

	int			i;

	int			j;

	LWLockPadded *lock;



	/* Initialize all individual LWLocks in main array */

	for (id = 0, lock = MainLWLockArray; id < NUM_INDIVIDUAL_LWLOCKS; id++, lock++)

		LWLockInitialize(&lock->lock, id);



	/* Initialize buffer mapping LWLocks in main array */

	lock = MainLWLockArray + BUFFER_MAPPING_LWLOCK_OFFSET;

	for (id = 0; id < NUM_BUFFER_PARTITIONS; id++, lock++)

		LWLockInitialize(&lock->lock, LWTRANCHE_BUFFER_MAPPING);



	/* Initialize lmgrs' LWLocks in main array */

	lock = MainLWLockArray + LOCK_MANAGER_LWLOCK_OFFSET;

	for (id = 0; id < NUM_LOCK_PARTITIONS; id++, lock++)

		LWLockInitialize(&lock->lock, LWTRANCHE_LOCK_MANAGER);



	/* Initialize predicate lmgrs' LWLocks in main array */

	lock = MainLWLockArray + PREDICATELOCK_MANAGER_LWLOCK_OFFSET;

	for (id = 0; id < NUM_PREDICATELOCK_PARTITIONS; id++, lock++)

		LWLockInitialize(&lock->lock, LWTRANCHE_PREDICATE_LOCK_MANAGER);



	/*

	 * Copy the info about any named tranches into shared memory (so that

	 * other processes can see it), and initialize the requested LWLocks.

	 */

	if (NamedLWLockTrancheRequests > 0)

	{

		char	   *trancheNames;



		NamedLWLockTrancheArray = (NamedLWLockTranche *)

			&MainLWLockArray[NUM_FIXED_LWLOCKS + numNamedLocks];



		trancheNames = (char *) NamedLWLockTrancheArray +

			(NamedLWLockTrancheRequests * sizeof(NamedLWLockTranche));

		lock = &MainLWLockArray[NUM_FIXED_LWLOCKS];



		for (i = 0; i < NamedLWLockTrancheRequests; i++)

		{

			NamedLWLockTrancheRequest *request;

			NamedLWLockTranche *tranche;

			char	   *name;



			request = &NamedLWLockTrancheRequestArray[i];

			tranche = &NamedLWLockTrancheArray[i];



			name = trancheNames;

			trancheNames += strlen(request->tranche_name) + 1;

			strcpy(name, request->tranche_name);

			tranche->trancheId = LWLockNewTrancheId();

			tranche->trancheName = name;



			for (j = 0; j < request->num_lwlocks; j++, lock++)

				LWLockInitialize(&lock->lock, tranche->trancheId);

		}

	}

}

2.1 LWLock赋初值
分别对LWLock中的成员变量state(包括exclusive、shared等状态)、tranche_id(锁的粒度划分,一个tranche_name下可以有多个lock,每个lock有唯一的tranche_id)、waiters(等待队列,是双向链表实现)进行初始化,当中state成员比较特殊。

/*

 * LWLockInitialize - initialize a new lwlock; it's initially unlocked

 */

void

LWLockInitialize(LWLock *lock, int tranche_id)

{

	pg_atomic_init_u32(&lock->state, LW_FLAG_RELEASE_OK);

#ifdef LOCK_DEBUG

	pg_atomic_init_u32(&lock->nwaiters, 0);

#endif

	lock->tranche = tranche_id;

	proclist_init(&lock->waiters);

}
/*

 * Code outside of lwlock.c should not manipulate the contents of this

 * structure directly, but we have to declare it here to allow LWLocks to be

 * incorporated into other data structures.

 */

对于LWLock不应该外部操作,但是有些结构体需要包含这一LWLock类型,所以把LWLock结构声明放在/src/include/storage/lwlock.h

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;

使用lwlock时,会有多个进程进行争用,因此需要保证对该变量的修改原子化,然而在初始化时还未有并发的使用,所以只是简单的赋值,但是该变量要保证修改完成时对其它进程的可见性,所以必须加上volatile关键字,后续每个进程的访问和修改都要从内存读取。

typedef struct pg_atomic_uint32

{

	volatile uint32 value;

} pg_atomic_uint32;
/*

 * pg_atomic_init_u32 - initialize atomic variable

 *

 * Has to be done before any concurrent usage..

 *

 * No barrier semantics.

 */

static inline void

pg_atomic_init_u32(volatile pg_atomic_uint32 *ptr, uint32 val)

{

	AssertPointerAlignment(ptr, 4);



	pg_atomic_init_u32_impl(ptr, val);

}

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

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

相关文章

android 如何分析应用的内存(十六)——使用AS查看Android堆

android 如何分析应用的内存&#xff08;十六&#xff09;——使用AS查看Android堆 在前面&#xff0c;先介绍了如何使用jdb和VS code查看应用栈相关内容。 本文将介绍&#xff0c;如何查看堆中的内容。大概有&#xff1a; 堆中的对象&#xff0c;有哪些堆中的对象&#xff0…

“Can‘t open perl script configure : No such file or directory”的解决办法

编译OpenSSL的时候执行到 perl configure 时提示找不到configure&#xff0c; 然后在网上搜了搜&#xff0c;大家给的解决办法一般都是说设置环境变量或者指定configure路径再执行&#xff1b;我试了都不行&#xff0c; 最后我把perl卸了重装就正常了&#xff1b; 然后我换了…

QEMU源码全解析32 —— Machine(2)

接前一篇文章&#xff1a;QEMU源码全解析31 —— Machine&#xff08;1&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 上一篇文章给m…

【力扣每日一题】2023.8.11 矩阵对角线元素的和

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们一个矩阵&#xff0c;让我们把矩阵对角线上的元素都加起来返回。 那么矩阵的对角线是有两条的&#xff0c;一条是从左上到右下…

Maven安装与配置教程

目录 一、前言 1.什么是Maven 2.为什么要使用Maven 二、Maven安装与配置 1.官网下载 2.Maven配置 3.修改Maven仓库下载镜像及修改仓库位置 3.1.修改仓库下载镜像地址 3.2.修改默认Maven的仓库位置 三、eclipse配置Maven 四、eclipse部署Maven项目 注意事项&#xff…

Python非线性全局优化

文章目录 全局优化函数简介详解性能测试 全局优化函数简介 scipy的optimize模块非常强大&#xff0c;也是我个人使用最多的scipy模块&#xff0c;这里面封装的都是成熟且高效的算法&#xff0c;久经考验。对于参加数学竞赛的同学来说&#xff0c;辛辛苦苦撸出来的遗传算法、模…

Eudic欧路词典 for Mac v4.4.5增强版

欧路词典 (Eudic)是一个功能强大的英语学习工具&#xff0c;它包含了丰富的英语词汇、短语和例句&#xff0c;并提供了发音、例句朗读、单词笔记等功能。 多语种支持&#xff1a;欧路词典支持多种语言&#xff0c;包括英语、中文、日语、法语等等&#xff0c;用户可以方便地进…

Kubernetes 调度 约束

调度约束 Kubernetes 是通过 List-Watch 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件&#xff0c;向 APIServer 发送命令&#xff0c;在 Node 节点上面建立 Pod 和 Container。 APIServer…

python——案例14:斐波那契数列

兔子生殖为例子而引入&#xff0c;故又称“兔子数列”&#xff0c; 其数值为&#xff1a;1、1、2、3、5、8、13、21、34……在数学上&#xff0c; 这一数列以如下递推的方法定义&#xff1a; F(0)1&#xff0c;F(1)1, F(n)F(n - 1)F(n - 2)&#xff08;n ≥ 2&#xff0c;n ∈ …

液体神经网络:LNN是个啥概念?

一、说明 在在人工智能领域&#xff0c;神经网络已被证明是解决复杂问题的非常强大的工具。多年来&#xff0c;研究人员不断寻求创新方法来提高其性能并扩展其能力。其中一种方法是液体神经网络&#xff08;LNN&#xff09;的概念&#xff0c;这是一个利用动态计算功能的迷人框…

Simpack助力中国铁路创新发展

中国铁路尤其是高铁的迅速发展是中国装备制造业走向世界一张名片&#xff0c;不仅为人们出行提供了便利&#xff0c;也为中国经济的快速增长提供了有力的支撑。同时&#xff0c;高速铁路的发展给产品研发带来了新的课题和挑战。尤其在动力学领域&#xff0c;各部件或子系统之间…

DP(区间DP)

石子合并 设有 N 堆石子排成一排&#xff0c;其编号为 1,2,3,…,N。 每堆石子有一定的质量&#xff0c;可以用一个整数来描述&#xff0c;现在要将这 N 堆石子合并成为一堆。 每次只能合并相邻的两堆&#xff0c;合并的代价为这两堆石子的质量之和&#xff0c;合并后与这两堆…

远程通信-RPC

项目场景&#xff1a; 在分布式微服务架构中&#xff0c;远程通信是最基本的需求。 常见的远程通信方式&#xff0c;有基于 REST 架构的 HTTP协议、RPC 框架。 下面&#xff0c;从三个维度了解一下 RPC。 1、什么是远程调用 2、什么是 RPC 3、RPC 的运用场景和优 什么是远程调用…

树莓派第一次开机

文章目录 基于树莓派的OpenEuler基础实验一一、树莓派介绍树莓派较普通电脑的优势1、廉价便携可折腾2、树莓派运行开源的Linux操作系统3、编程好平台4、开源大社区5、引脚可编程6、便携随身带7、灵活可扩展 二、openEuler embedded介绍三、树莓派开机指南1. 硬件准备2. 软件准备…

ROS入门-使用常用的ROS命令行工具:操作节点、话题、服务、消息和参数

目录 使用常用的ROS命令行工具&#xff1a;操作节点、话题、服务、消息和参数 1. rosnode&#xff1a;操作节点 2. rostopic&#xff1a;操作话题 3. rosservice&#xff1a;操作服务 4. rosmsg&#xff1a;操作msg消息 5. rossrv&#xff1a;操作srv消息 6. rosparam&am…

MySQL 存储过程、函数、触发器、事件

​ 目录 存储过程 创建存储过程 调用存储过程 查看存储过程 删除存储过程 进阶 变量 if条件判断 传递参数 case结构 while循环 repeat结构 loop语句 leave语句 游标/光标 存储函数 触发器 创建触发器 删除触发器 查看触发器 事件 查看事件调度器是否开启…

eNSP:ebgp和bgp的基础运用

实验要求&#xff1a; 拓扑图&#xff1a; 命令操作&#xff1a; r1: <Huawei>sys [Huawei]sys r1 [r1]int g 0/0/1 [r1-GigabitEthernet0/0/1]ip add 12.1.1.1 24 [r1-GigabitEthernet0/0/1]int lo0 [r1-LoopBack0]ip add 1.1.1.1 24[r2]ospf 1 router-id 2.2.2.2 [r2…

肉豆蔻酰五肽-8——祛眼袋和黑眼圈

肉豆蔻酰五肽-8 简介 眼袋和黑眼圈形成的原因&#xff1a; 1. 随着年龄的增大眼部皮肤会失去弹性, 眼部肌肉同时也会松弛, 从而在眼脸形成皱褶。衬垫在眼眶的脂肪从眼腔转移出并在眼脸聚集。袋状眼脸医学上称为皮肤松垂, 通常可以通过眼脸成形术得到改善。 2. 眼袋形成另外一…

【2023年11月第四版教材】《第2章-信息技术发展(合集篇)》

《第2章-信息技术发展&#xff08;第一部分&#xff09;》 章节说明1 计算机软硬件2 计算机网络2.1 网络的作用范围2.2 OSI模型2.3 广域网协议2.4 网络协议2.5 TCP/IP2.6 软件定义网络&#xff08;SDN&#xff09;2.7 第五代移动通信技术 3 存储和数据库3.1 存储系统架构3.2 存…

能化校对软件:提高招标文件质量的创新解决方案

智能化校对软件是一种创新的解决方案&#xff0c;可以进一步提高招标文件的质量和准确性。 以下是一些智能化校对软件的创新功能和优势&#xff1a; 1.自然语言处理(NLP)技术&#xff1a;智能化校对软件利用NLP技术来理解和分析文本&#xff0c;识别和纠正更复杂的语法和语义错…