【MyDB】4-VersionManager 之 3-死锁及超时检测

【MyDB】4-VersionManager 之 3-死锁及超时检测

  • 死锁及超时检测
    • 案例背景
    • LockTable
    • 锁请求与等待管理 `add`
      • vm调用add
      • putIntoList,isInList,removeFromList
    • 死锁检测 hasDeadLock方法
    • 资源释放与重分配
  • 参考资料

死锁及超时检测

本章涉及代码:top/xianghua/mydb/server/vm/LockTable.java

案例背景

学习如何使用 LockTable 进行锁管理、死锁检测与解决之前,先了解一下死锁是如何发生的。假设我们有三个事务 T1、T2 和 T3,它们分别要访问两个资源 R1 和 R2。事务的执行顺序如下:

  1. T1 锁定 R1:然后尝试锁定 R2。
  2. T2 锁定 R2:然后尝试锁定 R1。
  3. T3 尝试锁定 R1

这种情况下,T1 和 T2 之间会产生死锁,而 T3 将会被阻塞在等待 R1 上。

执行过程

  1. T1 锁定 R1
    • T1 请求锁定资源 R1。
    • 因为 R1 未被占用,所以 LockTable 将 R1 锁定给 T1。
    • T1 继续执行,准备请求锁定 R2。
  2. T2 锁定 R2
    • T2 请求锁定资源 R2。
    • 因为 R2 未被占用,所以 LockTable 将 R2 锁定给 T2。
    • T2 继续执行,准备请求锁定 R1。
  3. T1 请求锁定 R2
    • T1 请求锁定 R2。
    • 由于 R2 已被 T2 锁定,T1 被加入到 R2 的等待队列中。
  4. T2 请求锁定 R1
    • T2 请求锁定 R1。
    • 由于 R1 已被 T1 锁定,T2 被加入到 R1 的等待队列中。
    • 现在形成了 T1 → R2 → T2 → R1 → T1 的循环等待,导致死锁。
  5. T3 尝试锁定 R1
    • T3 请求锁定 R1。
    • 由于 R1 已被 T1 锁定且 T2 在等待 R1,T3 被加入到 R1 的等待队列中。

LockTable

上一章提到了2PL会阻塞事务,直至持有锁的线程释放锁。这样就可能出现死锁。因此,在MyDB中需要能检测死锁。

我们可以将事务之间的这种等待关系抽象成有向边,例如 T j T_j Tj在等待 T i T_i Ti。这样,无数的有向边就可以形成一个图(不一定是连通图)。检测死锁的话只需要查看图中是否有环。

就MyDB而言,使用了一个LockTabel对象,通过在内存中维护这张图,而记录事务的等待关系。

注意,x2u和wait是列表,分别记录xid已经获得的资源的uid列表,和正在等待某个uid的xid列表。

public class LockTable {private Map<Long, List<Long>> x2u;  // 某个XID已经获得的资源的UID列表private Map<Long, Long> u2x;        // UID被某个XID持有private Map<Long, List<Long>> wait; // 正在等待UID的XID列表private Map<Long, Lock> waitLock;   // 正在等待资源的XID的锁private Map<Long, Long> waitU;      // XID正在等待的UIDprivate Lock lock;...
}

锁请求与等待管理 add

当一个事务请求获取某个资源时,LockTable 首先会检查该资源是否已被其他事务持有。如果没有被持有,资源将直接分配给请求的事务。如果资源已被占用,事务将进入等待状态,并存储在相应的等待队列中。

  1. 检查当前事务xid是否已经获得了数据uid(isInList(x2u, xid, uid)

  2. 判断uid是否被其他xid持有,u2x.containsKey(uid),假如未被其他xid持有,则直接获得

    u2x.put(uid, xid); // Map<Long, Long> u2x; UID被某个XID持有

    putIntoList(x2u, xid, uid); //Map<Long, List> x2u 某个XID已经获得的资源的UID列表

  3. 被持有则等待,向依赖等待图中添加当前等待关系,即waitU.put(xid, uid);,之后进行死锁检测hasDeadLock。如果检测到死锁,就撤销这条边,不允许添加,并撤销该事务。

    // 不需要等待则返回null,否则返回锁对象// 会造成死锁则抛出异常public Lock add(long xid, long uid) throws Exception {// 1. 加锁lock.lock();try {// 2. xid是否已经获得了uid,是则说明不需要等待,返回nullif(isInList(x2u, xid, uid)) {return null;}// 3. uid是否被其他xid持有if(!u2x.containsKey(uid)) {// 3.1 uid未被其他xid持有,则直接获得u2x.put(uid, xid);putIntoList(x2u, xid, uid);return null;}// 3.2 uid被其他xid持有,则等待waitU.put(xid, uid);//putIntoList(wait, xid, uid);putIntoList(wait, uid, xid); // 这里是为了方便死锁检测,因为死锁检测是从等待队列中选择一个xid来占用uidif(hasDeadLock()) {waitU.remove(xid);removeFromList(wait, uid, xid);throw Error.DeadlockException;}Lock l = new ReentrantLock();l.lock();waitLock.put(xid, l);return l;} finally {lock.unlock();}}

vm调用add

在vm中,当某个事务xid需要资源uid时,就会调用add,可以看到,add方法需要等待的时候,会返回一个上了锁的Lock对象。调用方在获取到该对象时,需要尝试获取该对象的锁,由此实现阻塞线程的目的,例如:

Lock l = lt.add(xid, uid);
if(l != null) {l.lock();   // 阻塞在这一步l.unlock();
}

putIntoList,isInList,removeFromList

由于之前说过,我们再复习一下LockTable中的x2u和wait是long->List ,

private Map<Long, List> x2u; // 某个XID已经获得的资源的UID列表

private Map<Long, List> wait; // 正在等待UID的XID列表

那当我们需要知道事务是否已经获得资源uid时,由于x2u.get(xid)得到的时一个资源list,因此,需要使用inInList来判断,uid是否在xid已经获得的资源list中。

同理,当需要向x2u中添加某个xid已经获得的资源uid,也不能直接添加,而是要先获得x2u已经获得的资源的uid list,之后再向其中添加,就用到了putIntoList方法。

public class LockTable {private Map<Long, List<Long>> x2u;  // 某个XID已经获得的资源的UID列表private Map<Long, Long> u2x;        // UID被某个XID持有private Map<Long, List<Long>> wait; // 正在等待UID的XID列表private Map<Long, Lock> waitLock;   // 正在等待资源的XID的锁private Map<Long, Long> waitU;      // XID正在等待的UIDprivate Lock lock;...
}

putIntoList

    /*** 将uid1添加到uid0对应的列表中* @param listMap* @param id0* @param id1*/private void putIntoList(Map<Long, List<Long>> listMap, long id0, long id1) {// 如果id0对应的列表不存在,则创建一个新的列表if(!listMap.containsKey(id0)) {listMap.put(id0, new ArrayList<>());}// 将id1添加到id0对应的列表中listMap.get(id0).add(0, id1);}

isInList(listMap,id0,id1)id1是否在id0对应的列表中

/*** 判断id1是否在id0对应的列表中* @param listMap* @param id0* @param id1* @return*/private boolean isInList(Map<Long, List<Long>> listMap, long id0, long id1) {List<Long> l = listMap.get(id0);if(l == null) return false;Iterator<Long> i = l.iterator();while(i.hasNext()) {long e = i.next();if(e == id1) {return true;}}return false;}

removeFromList从uid0对应的列表中删除uid1

    /*** 从uid0对应的列表中删除uid1* @param listMap* @param uid0* @param uid1*/private void removeFromList(Map<Long, List<Long>> listMap, long uid0, long uid1) {List<Long> l = listMap.get(uid0);if(l == null) return;Iterator<Long> i = l.iterator();while(i.hasNext()) {long e = i.next();if(e == uid1) {i.remove();break;}}if(l.size() == 0) {listMap.remove(uid0);}}

死锁检测 hasDeadLock方法

为了避免死锁,LockTable 实现了基于深度优先搜索(DFS)的死锁检测机制。通过遍历事务依赖图,系统可以检测到是否存在循环依赖,从而识别死锁。

需要注意这个图不一定是连通图。思路就是为每个节点设置一个访问戳xidStamp,都初始化为 -1,随后遍历所有节点,以每个非 -1 的节点作为根进行深搜,并将深搜该连通图中遇到的所有节点都设置为同一个数字,不同的连通图数字不同。这样,如果在遍历某个图时,遇到了之前遍历过的节点,说明出现了环。

在这里插入图片描述

    private Map<Long, Integer> xidStamp; // XID对应的stampprivate int stamp; // 当前stampprivate boolean hasDeadLock() {xidStamp = new HashMap<>();stamp = 1;for(long xid : x2u.keySet()) {Integer s = xidStamp.get(xid);if(s != null && s > 0) {continue;}stamp ++;if(dfs(xid)) {return true;}}return false;}private boolean dfs(long xid) {Integer stp = xidStamp.get(xid);if(stp != null && stp == stamp) {return true;}if(stp != null && stp < stamp) {return false;}xidStamp.put(xid, stamp);Long uid = waitU.get(xid); // 得到事务xid等待的资源uidif(uid == null) return false; // 没有等待的资源Long x = u2x.get(uid); //找到占用资源uid的事务xassert x != null;return dfs(x); // 继续搜索事务x}

可以在LockTabelTest中运行testLockTable以检测死锁的发生

    public void testLockTable() {LockTable lt = new LockTable();try {lt.add(1, 1);} catch (Exception e) {Panic.panic(e);}try {lt.add(2, 2);} catch (Exception e) {Panic.panic(e);}try {lt.add(2, 1);} catch (Exception e) {Panic.panic(e);}try {lt.add(1, 2);} catch (Exception e) {Panic.panic(e);}assertThrows(RuntimeException.class, ()->lt.add(1, 2));}

资源释放与重分配

在一个事务 commit 或者 abort 时,就可以释放所有它持有的锁,并将自身从等待图中删除。然后将资源锁重新分配给等待队列中的其他事务。

while 循环释放掉了这个线程所有持有的资源的锁,这些资源可以被等待的线程所获取:

    public void remove(long xid) {lock.lock();try {// 1. 释放xid所占用的资源列表uidsList<Long> uids = x2u.get(xid);if(uids != null) {while(uids.size() > 0) {Long uid = uids.remove(0);selectNewXID(uid); //   从等待队列中选择一个xid来占用uid}}// 2. 在waitU(正在等待资源的xid),x2u(某个XID已经获得的资源的UID列表),waitLock中删除xid:waitU.remove(xid);x2u.remove(xid);waitLock.remove(xid);} finally {lock.unlock();}}

selectNewXID

从 List 开头开始尝试解锁,还是个公平锁。解锁时,将该 Lock 对象 unlock 即可,这样业务线程就获取到了锁,就可以继续执行了。

    // 从等待队列中选择一个xid来占用uidprivate void selectNewXID(long uid) {// u2x:uid被某个xid持有// 1. 从u2x中释放uid,标记uid不再被xid占用u2x.remove(uid);// 2. 获取该uid的等待队列xidsList<Long> xids = wait.get(uid); // wait:uid被哪些xid等待if(xids == null) return;assert xids.size() > 0;// 3. 从等待队列中选择一个xid来占用uidwhile(xids.size() > 0) {long xid = xids.remove(0);if(!waitLock.containsKey(xid)) {continue;} else {u2x.put(uid, xid);Lock lo = waitLock.remove(xid);waitU.remove(xid);lo.unlock();break;}}if(xids.size() == 0) wait.remove(uid);}

参考资料

死锁及超时检测 | EasyDB (blockcloth.cn)

MYDB 7. 死锁检测与 VM 的实现 | 信也のブログ (shinya.click)

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

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

相关文章

Elasticsearch:如何搜索含有复合词的语言

作者&#xff1a;来自 Elastic Peter Straer 复合词在文本分析和标记过程中给搜索引擎带来挑战&#xff0c;因为它们会掩盖词语成分之间的有意义的联系。连字分解器标记过滤器等工具可以通过解构复合词来帮助解决这些问题。 德语以其长复合词而闻名&#xff1a;Rindfleischetik…

服务器虚拟化实战:架构、技术与最佳实践

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 服务器虚拟化是现代 IT 基础设施的重要组成部分&#xff0c;通过虚拟化技术可以提高服务器资源利用率、降低硬件成本&am…

【LLM】Ollama框架入门指北

note Ollama是一个开源框架&#xff0c;专门设计用于在本地运行大型语言模型。它的主要特点是将模型权重、配置和数据捆绑到一个包中&#xff0c;从而优化了设置和配置细节&#xff0c;包括GPU使用情况&#xff0c;简化了在本地运行大型模型的过程。Ollama提供了对模型量化的支…

Linux系统:Ubuntu替换镜像源具体方法;

在Linux系统更新下载软件时&#xff0c;如遇因镜像源问题下载失败时&#xff0c;我们就需要替换系统原有镜像源&#xff0c;那么&#xff0c;此时&#xff0c;你是否还在百度四处搜索可以用的镜像源地址&#xff0c;然后反复去测试源地址的正确性呢&#xff0c;下面介绍一个亲测…

使用vhd虚拟磁盘安装两个win10系统

使用vhd虚拟磁盘安装两个win10系统 前言vhd虚拟磁盘技术简介准备工具开始动手实践1.winX选择磁盘管理2.选择“操作”--“创建VHD”3.自定义一个位置&#xff0c;输入虚拟磁盘大小4.右键初始化磁盘5.选择GPT分区表格式6.右键新建简单卷7.给卷起个名字&#xff0c;用于区分8.打开…

HTML(快速入门)

欢迎大家来到我的博客~欢迎大家对我的博客提出指导&#xff0c;有错误的地方会改进的哦~点击这里了解更多内容 目录 一、前言二、HTML基础2.1 什么是HTML?2.2 认识HTML标签2.2.1 HTML标签当中的基本结构2.2.2 标签层次结构 2.3 HTML常见标签2.3.1 标题标签2.3.2 段落标签2.3.3…

d3.js: Relation Graph

d3.js Tags d3/d3 GitHub D3 by Observable | The JavaScript library for bespoke data visualization 下载或 <!-- 引入 D3.js 库 --> <script src"https://d3js.org/d3.v7.min.js"></script> <!-- 引入 D3.js 库 --> <…

Oracle Primavera P6自动进行进度计算

前言 在P6 Professional 有一个自动计划计算的选项&#xff0c;很多人不了解该设置如何使用&#xff0c;以及什么时候该启动这项配置。 详情 P6 Professional 默认为非自动进度计算。启用自动选项后&#xff0c;可以快速查看调度更改的效果。 ​ ​ 如图所示&#xff0c;当你…

反射、枚举以及lambda表达式

一.反射 1.概念&#xff1a;Java的反射&#xff08;reflection&#xff09;机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff0c;既然能拿到那么&am…

【Proteus仿真】【51单片机】简易计算器系统设计

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 联系作者 一、主要功能 1、LCD1602液晶显示 2、矩阵按键​ 3、可以进行简单的加减乘除运算 4、最大 9999*9999 二、使用步骤 系统运行后&#xff0c;LCD1602显示数据&#xff0c;通过矩阵按键…

HarmonyOS简介:HarmonyOS核心技术理念

核心理念 一次开发、多端部署可分可合、自由流转统一生态、原生智能 一次开发、多端部署 可分可合 自由流转 自由流转可分为跨端迁移和多端协同两种情况 统一生态 支持业界主流跨平台开发框架&#xff0c;通过多层次的开放能力提供统一接入标准&#xff0c;实现三方框架快速…

(即插即用模块-特征处理部分) 十九、(NeurIPS 2023) Prompt Block 提示生成 / 交互模块

文章目录 1、Prompt Block2、代码实现 paper&#xff1a;PromptIR: Prompting for All-in-One Blind Image Restoration Code&#xff1a;https://github.com/va1shn9v/PromptIR 1、Prompt Block 在解决现有图像恢复模型时&#xff0c;现有研究存在一些局限性&#xff1a; 现有…

Day24-【13003】短文,数据结构与算法开篇,什么是数据元素?数据结构有哪些类型?什么是抽象类型?

文章目录 13003数据结构与算法全书框架考试题型的分值分布如何&#xff1f; 本次内容概述绪论第一节概览什么是数据、数据元素&#xff0c;数据项&#xff0c;数据项的值&#xff1f;什么是数据结构&#xff1f;分哪两种集合形式&#xff08;逻辑和存储&#xff09;&#xff1f…

使用 MSYS2 qemu 尝鲜Arm64架构国产Linux系统

近期&#xff0c;我的师弟咨询我关于Arm64架构的国产CPU国产OS开发工具链问题。他们公司因为接手了一个国企的单子&#xff0c;需要在这类环境下开发程序。说实在的我也没有用过这个平台&#xff0c;但是基于常识&#xff0c;推测只要基于C和Qt&#xff0c;应该问题不大。 1. …

unity学习21:Application类与文件存储的位置

目录 1 unity是一个跨平台的引擎 1.1 使用 Application类&#xff0c;去读写文件 1.2 路径特点 1.2.1 相对位置/相对路径&#xff1a; 1.2.2 固定位置/绝对路径&#xff1a; 1.3 测试方法&#xff0c;仍然挂一个C#脚本在gb上 2 游戏数据文件夹路径&#xff08;只读&…

【Redis】hash 类型的介绍和常用命令

1. 介绍 Redis 中存储的 key-value 本身就是哈希表的结构&#xff0c;存储的 value 也可以是一个哈希表的结构 这里每一个 key 对应的一个 哈希类型用 field-value 来表示 2. 常用命令 命令 介绍 时间复杂度 hset key field value 用于设置哈希表 key 中字段 field 的值为…

基于51单片机和WS2812B彩色灯带的流水灯

目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码四、主函数总结 系列文章目录 前言 用彩色灯带按自己想法DIY一条流水灯&#xff0c;谁不喜欢呢&#xff1f; 所用单片机&#xff1a;STC15W204S &#xff08;也可以用其他1T单片机&#xff0c;例如&#xff0c;S…

力扣017_最小覆盖字串题解----C++

题目描述 我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针&#xff0c;一个用于「延伸」现有窗口的 r 指针&#xff0c;和一个用于「收缩」窗口的 l 指针。在任意时刻&#xff0c;只有一个指针运动&#xff0c;而另一个保持静止。我们在 s 上滑动…

如何从客观角度批判性阅读分析博客

此文仅以个人博客为例&#xff0c;大量阅读朋友反馈给我的交流让我得知他们所理解我的博客所表达的意思并非我所想表达的&#xff0c;差异或大或小&#xff0c;因人而异。 观点与事实 只有从客观角度反复批判性阅读和分析&#xff0c;才能逐渐清晰观点和事实。 观点不等于事实…

深入理解MySQL 的 索引

索引是一种用来快速检索数据的一种结构, 索引使用的好不好关系到对应的数据库性能方面, 这篇文章我们就来详细的介绍一下数据库的索引。 1. 页面的大小: B 树索引是一种 Key-Value 结构&#xff0c;通过 Key 可以快速查找到对应的 Value。B 树索引由根页面&#xff08;Root&am…