ToB企服应用市场:ToB评测及商务社交产业平台
标题:
openGauss数据库源码解析系列文章—— 事件机制源码解析(二)_opengauss数
[打印本页]
作者:
张春
时间:
2025-2-21 02:36
标题:
openGauss数据库源码解析系列文章—— 事件机制源码解析(二)_opengauss数
int requested[MAX_LOCKMODES]; /* 请求锁的计数 */
int nRequested; /* requested数组总数 */
int granted[MAX_LOCKMODES]; /* 已获取锁的计数 */
int nGranted; /* granted数组总数 */
复制代码
} LOCK;
PROCLOCK结构,主要是将同一锁对象等待和持有者的线程信息串联起来的结构体。对应的代码如下:
复制代码
typedef struct PROCLOCK {
/* 标识
/
PROCLOCKTAG tag; /
proclock对象的唯一标识 */
/* 数据 */
LOCKMASK holdMask; /* 已获取锁类型的位掩码 */
LOCKMASK releaseMask; /* 预释放锁类型的位掩码 */
SHM_QUEUE lockLink; /* 指向锁对象链表的指针 */
SHM_QUEUE procLink; /* 指向PGPROC链表的指针 */
复制代码
} PROCLOCK;
t\_thrd.proc结构体里waitLock字段记录了该线程等待的锁,该结构体中procLocks字段将所有跟该锁有关的持有者和等着串起来,其队列关系如图5-16所示。

图5-16 t\_thrd.proc结构体队列关系图
常规锁的主要函数如下。
(1) LockAcquire:对锁对象加锁。
(2) LockRelease:对锁对象释放锁。
(3 )LockReleaseAll:释放所有锁资源。
### 5.3.4 死锁检测机制
死锁主要是由于进程B要访问进程A所在的资源,而进程A又由于种种原因不释放掉其锁占用的资源,从而数据库就会一直处于阻塞状态。如图5-17中,T1使用资源R1去请求R2,而T2事务持有R2的资源去申请R1。

图5-17 死锁状态
形成死锁的必要条件是:资源的请求与保持。每一个进程都可以在使用一个资源的同时去申请访问另一个资源。打破死锁的常见处理方式:中断其中的一个事务的执行,打破环状的等待。openGauss提供了LWLock的死锁检测及常规锁的死锁检测机制,下面简单介绍一下相关原理及代码。
#### 1. LWLock死锁检测自愈
openGauss使用一个独立的监控线程来完成轻量级锁的死锁探测、诊断和解除。工作线程在请求轻量级锁成功之前会写入一个时间戳数值,成功获得锁之后置该时间戳为0。监测线程可以通过快速对比时间戳数值来发现长时间获得不到锁资源的线程,这一过程是快速轻量的。只有发现长时间的锁等待,死锁检测的诊断才会触发。这么做的目的是防止频繁诊断影响业务正常执行能。一旦确定了死锁环的存在,监控线程首先会将死锁信息记录到日志中去,然后采取恢复措施使得死锁自愈,即选择死锁环中的一个线程报错退出。机制如图5-18所示。

图5-18 LWLock死锁检测自愈
因为检测死锁是否真正发生是一个重CPU操作,为了不影响数据库性能和运行稳定性,轻量级死锁检测使用了一种轻量式的探测,用来快速判断是否可能发生了死锁。采用看门狗(watchdog)的方法,利用时间戳来探测。工作线程在锁请求进入时会在全局内存上写入开始等待的时间戳;在锁请求成功后,将该时间戳清0。对于一个发生死锁的线程,它的锁请求是wait状态,时间戳也不会清0,且与当前运行时间戳数值的差值越来越大。由GUC参数fault\_mon\_timeout控制检测间隔时间,默认为5s。LWLock死锁检测每隔fault\_mon\_timeout去进行检测,如果当前发现有同样线程、同样锁id,且时间戳等待时间超过检测间隔时间值,则触发真正死锁检测。时间统计及轻量级检测函数如下。
(1) pgstat\_read\_light\_detect:从统计信息结构体中读取线程及锁id相关的时间戳,并记录到指针队列中。
(2) lwm\_compare\_light\_detect:跟几秒检测之前的时间对比,如果找到可能发生死锁的线程及锁id则返回true,否则返回false。
真正的LWLock死锁检测是一个有向无环图的判定过程,它的实现跟常规锁类似,这部分会在下面一小节中详细介绍。死锁检测需要两部分的信息:锁,包括请求和分配的信息;线程,包括等待和持有的信息,这些信息记录到相应的全局变量中,死锁监控线程可以访问并进行判断。相关的函数如下。
(1) lwm\_heavy\_diagnosis:检测是否有死锁。
(2) lwm\_deadlock\_report:报告死锁详细信息,方便定位诊断。
(3) lw\_deadlock\_auto\_healing:治愈死锁,选择环中一个线程退出。
用于死锁检测的锁和线程相关数据结构如下。
(1) lock\_entry\_id记录线程信息,有thread\_id及sessionid是为了适配线程池框架,可以准确的从统计信息中找到相应的信息。对应的代码如下:
复制代码
typedef struct {
ThreadId thread_id;
uint64 st_sessionid;
} lock_entry_id;
(2) lwm\_light\_detect记录可能出现死锁的线程,最后用一个链表的形式将当前所有信息串联起来。对应的代码如下:
复制代码
typedef struct {
/* 线程ID */
lock_entry_id entry_id;
/* 轻量级锁检测引用计数 */
int lw_count;
复制代码
} lwm_light_detect;
(3) lwm\_lwlocks 记录线程相关的锁信息,持有锁数量,以及等锁信息。对应的代码如下:
复制代码
typedef struct {
lock_entry_id be_tid; /* 线程ID
/
int be_idx; /
背景线程的位置
/
LWLockAddr want_lwlock; /
预获取锁的信息
/
int lwlocks_num; /
线程持有的轻量级锁个数
/
lwlock_id_mode
held_lwlocks; /* 线程持有的轻量级锁数组 */
} lwm_lwlocks;
#### 2. 常规锁死锁检测
openGauss在获取锁时如果没有冲突可以直接上锁;如果有冲突则设置一个定时器timer,并进入等待,过一段时间会被timer唤起进行死锁检测。如果在某个锁的等锁队列中,进程T2排在进程T1后面,且进程T2需要获取的锁与T1需要获取的锁资源冲突,则T2到T1会有一条软等待边(soft edge)。如果进程T2的加锁请求与T1进程所持有的锁冲突,则有一条硬等待边(hard edge)。那么整体思路就是通过递归调用,从当前顶点等锁的顶点出发,沿着等待边向前走,看是否存在环,如果环中有soft edge,说明环中两个进程都在等锁,重新排序,尝试解决死锁冲突。如果没有soft edge,那么只能终止当前等锁的事务,解决死锁等待环。如图5-19所示,虚线代表soft edge,实线代表hard fdge。线程A等待线程B,线程B等待线程C,线程C等待线程A,因为线程A等待线程B的是soft edge,进行一次调整成为图5-19右边的等待关系,此时发现线程A等待线程C,线程C等待线程A,没有soft edge,检测到死锁。

图5-19 常规锁死锁检测示意图
主要函数如下。
(1) DeadL
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4