数据管理_DM的实现

news2024/12/21 22:42:36

DataManager 的实现

DataManager 是数据库系统中的核心组件,负责管理底层数据的访问、修改和事务处理。它是 DM 层直接对外提供方法的类,用来对上层其他模块提供数据操作的API的,同时也实现了对 DataItem 对象的缓存管理。DataItem 存储的 key 是由页号和页内偏移组成的一个 8 字节无符号整数,页号和偏移各占 4 字节。

DataManager 的核心功能
  1. 数据缓存与管理DataManager 通过缓存 DataItem 对象,提供高效的数据访问。DataItemkey 是一个 8 字节无符号整数,其中页号和页内偏移各占 4 字节。这种设计允许快速定位和访问数据,减少了对底层存储的频繁访问。
  2. 数据访问与操作DataManager 提供了读取、插入和修改等数据操作方法。通过这些方法,数据库的上层模块可以方便地对数据进行操作。
  3. 事务管理DataManager 支持事务管理,确保数据操作的原子性。事务管理能够保证在事务提交或回滚时,数据的一致性和完整性。
  4. 日志记录与恢复DataManager 在数据修改操作前后执行日志记录,以确保数据的安全性和可靠性,并支持系统崩溃后的数据恢复。
  5. 页面索引管理DataManager 还实现了页面索引管理功能,通过页面索引,可以快速定位到合适的空闲空间,提高数据插入的效率和性能。
  6. 文件初始化与校验DataManager 在创建和打开数据库文件时,进行文件的初始化和校验,确保文件的正确性和完整性。

DataManager 的uid的生成与解析

DataItemDataManager 中的存储和管理是通过一个唯一标识符 Uid 来实现的。这个 Uid 是由页面编号 (pgno) 和页面内偏移量 (offset) 组成的一个 8 字节无符号整数,其中页号和偏移量各占 4 字节。高4字节的32位表示DataItem存储的Page的页号低4字节的32位中只有低16位有意义,这16位表示DataItem存储的Page中的页内偏移,而高16位无意义 , 这里以pgno = 2 和 offset = 0来演示生成和解析 Uid 的详细过程。

1、生成 Uid 通过将页面编号 (pgno) 和偏移量 (offset) 组合成一个 8 字节无符号整数来生成 Uid。这里使用了位移和按位或运算。

public class Types {
    public static long addressToUid(int pgno, short offset) {
        long u0 = (long) pgno;
        long u1 = (long) offset;
        return u0 << 32 | u1; // 左移32位表示页号,按位或运算将页号和偏移量合并成一个Uid
    }
}

按位或:有1为1;双0为0

2、从 Uid 中提取偏移量 (**offset**) 为了从 Uid 中提取出偏移量,需要对 Uid 进行按位与运算。偏移量是 Uid 的低 16 位,通过与 16 位全1(0xFFFF)进行按位与操作可以提取出偏移量。(双1为1;其余为0)

// 提取偏移量,偏移量占 Uid 的低16位
short offset = (short) (uid & ((1L << 16) - 1)); // 按位与操作提取出低16位的偏移量

3、从 Uid 中提取页面编号 (**pgno**) 提取页面编号则需要将 Uid 右移 32 位,以便将高 32 位对齐到低位,然后通过按位与操作提取出页面编号。

// 右移32位,将高32位对齐到低位
uid >>>= 32;
// 提取页面编号,页面编号占 Uid 的高32位
int pgno = (int) (uid & ((1L << 32) - 1)); // 按位与操作提取出页面编号

编码实现

DM结构定义
public class DataManagerImpl extends AbstractCache<DataItem> implements DataManager {
    TransactionManager tm; //TM事务管理器
    PageCache pc; //PC页面缓存
    Logger logger;  //数据库日志
    PageIndex pIndex;  //页面索引
    Page pageOne;  //第一页
创建新的DM(构造方法)
    public DataManagerImpl(PageCache pc, Logger logger, TransactionManager tm) {
        super(0);
        this.pc = pc;
        this.logger = logger;
        this.tm = tm;
        this.pIndex = new PageIndex();
    }
创建/打开DM
    /**
     * 创建页缓存(PageCache):通过调用PageCache.create(path, mem)方法,创建一个页缓存对象pc。path参数指定了数据存储的路径,mem参数指定了内存大小。
     * 创建日志记录器(Logger):通过调用Logger.create(path)方法,创建一个日志记录器对象lg。path参数同样指定了数据存储的路径。
     * 创建数据管理器实现类(DataManagerImpl)实例:使用创建的页缓存对象pc、日志记录器对象lg和事务管理器对象tm作为参数,创建一个数据管理器实现类DataManagerImpl的实例dm。
     * 初始化页一(PageOne):调用dm.initPageOne()方法,初始化数据管理器中的页一。页一是一个特殊的页,通常用于存储数据库的元数据或配置信息。
     * 返回数据管理器实例:将创建并初始化好的DataManagerImpl实例dm作为返回值,返回给调用者。这是数据库打开过程中的初始化阶段,其中创建了数据管理器所需的核心组件,并对关键的页进行了初始化操作。
     * @param path
     * @param mem
     * @param tm
     * @return
     */
    public static DataManager create(String path, long mem, TransactionManager tm) {
        PageCache pc = PageCache.create(path, mem); //111111
        Logger lg = Logger.create(path);

        DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);
        dm.initPageOne();
        return dm;
    }


//111111。缓存
    public static PageCacheImpl create(String path, long memory) {
        //1、根据路径创建db文件
        File f = new File(path+PageCacheImpl.DB_SUFFIX);
        try {
            //2、判断文件是否能创建成功
            if(!f.createNewFile()) {
                Panic.panic(Error.FileExistsException);
            }
        } catch (Exception e) {
            Panic.panic(e);
        }
        //3、文件是否可读写
        if(!f.canRead() || !f.canWrite()) {
            Panic.panic(Error.FileCannotRWException);
        }
        //4、创建NIO管道和随机文件对象操作类创建的文件
        FileChannel fc = null;
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(f, "rw");
            fc = raf.getChannel();
        } catch (FileNotFoundException e) {
           Panic.panic(e);
        }
        //5、创建PageCacheImpl对象
        return new PageCacheImpl(raf, fc, (int)memory/PAGE_SIZE);
    }



    public static DataManager open(String path, long mem, TransactionManager tm) {
        PageCache pc = PageCache.open(path, mem);//2222
        Logger lg = Logger.open(path);
        DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);
        if(!dm.loadCheckPageOne()) {
            Recover.recover(tm, lg, pc);
        }
        dm.fillPageIndex();
        PageOne.setVcOpen(dm.pageOne);
        dm.pc.flushPage(dm.pageOne);

        return dm;
    }


//222222222222

public static PageCacheImpl open(String path, long memory) {
        File f = new File(path+PageCacheImpl.DB_SUFFIX);
        if(!f.exists()) { //判断文件是否存在
            Panic.panic(Error.FileNotExistsException);
        }
        if(!f.canRead() || !f.canWrite()) {
            Panic.panic(Error.FileCannotRWException);
        }

        FileChannel fc = null;
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(f, "rw");
            fc = raf.getChannel();
        } catch (FileNotFoundException e) {
           Panic.panic(e);
        }
        return new PageCacheImpl(raf, fc, (int)memory/PAGE_SIZE);
    }



如上两种不同方式需要注意:

  • 从空文件创建需要对第一页进行初始化

  • 从已有文件创建(即打开),需要对第一页进行校验从而判断是否需要执行恢复流程,并重新对第一页生成随机字节

初始化PageIndex
    // 初始化pageIndex
    void fillPageIndex() {
        //获取当前的pageCache中的页面数量
        int pageNumber = pc.getPageNumber();
        //遍历从第二页开始的每一页
        for(int i = 2; i <= pageNumber; i ++) {
            Page pg = null;
            try {
                //获取第i页
                pg = pc.getPage(i);
            } catch (Exception e) {
                Panic.panic(e);
            }
            //获取第i页的空闲空间大小,将第i页的页面编号和空闲空间大小添加到 PageIndex 中
            pIndex.add(pg.getPageNumber(), PageX.getFreeSpace(pg));
            pg.release();
        }
    }


这个方法的主要目的是在数据库打开时,为数据管理器构建一个页索引,以便后续的数据操作可以快速定位和访问页

加载并检查PageOne
getForCache

也是继承自AbstractCache,只需要从 key 中解析出页号,从 pageCache 中获取到页面,再根据偏移,解析出 DataItem 即可

@Override
protected DataItem getForCache(long uid) throws Exception {
    // 从 uid 中提取出偏移量(offset),这是通过位操作实现的,偏移量是 uid 的低16位
    short offset = (short) (uid & ((1L << 16) - 1));
    // 将 uid 右移32位,以便接下来提取出页面编号(pgno)
    uid >>>= 32;
    // 从 uid 中提取出页面编号(pgno),页面编号是 uid 的高32位
    int pgno = (int) (uid & ((1L << 32) - 1));
    // 使用页面缓存(pc)的 getPage(int pgno) 方法根据页面编号获取一个 Page 对象
    Page pg = pc.getPage(pgno);
    // 使用 DataItem 接口的静态方法 parseDataItem(Page pg, short offset, DataManagerImpl dm)
    // 根据获取到的 Page 对象、偏移量和当前的 DataManagerImpl 对象(this)解析出一个 DataItem 对象,并返回这个对象
    return DataItem.parseDataItem(pg, offset, this);
}
releaseForCache

DataItem 缓存释放,需要将 DataItem 写回数据源,由于对文件的读写是以页为单位进行的,只需要将 DataItem 所在的页 release 即可:

@Override
protected void releaseForCache(DataItem di) {
    di.page().release();
}
DataManager的核心方法
  1. 读取数据read()
  • 根据 Uid 从缓存中获取 DataItem,并校验其有效性。DataItemUid 是由页号和页内偏移组成的一个 8 字节无符号整数。

@Override
public DataItem read(long uid) throws Exception {
    //从缓存页面中读取到DataItemImpl
    DataItemImpl di = (DataItemImpl) super.get(uid); // 若缓存中不存在则调用 getForCache() 方法
    //校验di是否有效
    if (!di.isValid()) {
        // 无效释放缓存
        di.release();
        return null;
    }
    return di;
}

2.插入数据insert()

  • PageIndex 中选择一个合适的页面进行插入操作,记录插入日志,并返回插入位置的偏移。插入的位置和页面信息都是通过页号和偏移量进行管理的。

@Override
public long insert(long xid, byte[] data) throws Exception {
    // 将输入的数据包装成DataItem的原始格式
    byte[] raw = DataItem.wrapDataItemRaw(data);
    // 如果数据项的大小超过了页面的最大空闲空间,抛出异常
    if (raw.length > PageX.MAX_FREE_SPACE) {
        throw Error.DataTooLargeException;
    }

    // 初始化一个页面信息对象
    PageInfo pi = null;
    // 尝试5次找到一个可以容纳新数据项的页面
    for (int i = 0; i < 5; i++) {
        // 从页面索引中选择一个可以容纳新数据项的页面
        pi = pIndex.select(raw.length);
        // 如果找到了合适的页面,跳出循环
        if (pi != null) {
            break;
        } else {
            // 如果没有找到合适的页面,创建一个新的页面,并将其添加到页面索引中
            int newPgno = pc.newPage(PageX.initRaw());
            pIndex.add(newPgno, PageX.MAX_FREE_SPACE);
        }
    }
    // 如果还是没有找到合适的页面,抛出异常
    if (pi == null) {
        throw Error.DatabaseBusyException;
    }

    // 初始化一个页面对象
    Page pg = null;
    // 初始化空闲空间大小为0
    int freeSpace = 0;
    try {
        // 获取页面信息对象中的页面
        pg = pc.getPage(pi.pgno);
        // 生成插入日志
        byte[] log = Recover.insertLog(xid, pg, raw);
        // 将日志写入日志文件
        logger.log(log);

        // 在页面中插入新的数据项,并获取其在页面中的偏移量
        short offset = PageX.insert(pg, raw);

        // 释放页面
        pg.release();
        // 返回新插入的数据项的唯一标识符
        return Types.addressToUid(pi.pgno, offset);

    } finally {
        // 将页面重新添加到页面索引中
        if (pg != null) {
            pIndex.add(pi.pgno, PageX.getFreeSpace(pg));
        } else {
            pIndex.add(pi.pgno, freeSpace);
        }
    }
}

/**
 *  返回一个完整的 DataItem 结构数据
 *  dataItem 结构如下:
 *  [ValidFlag] [DataSize] [Data]
 *  ValidFlag 1字节,0为合法,1为非法
 *  DataSize  2字节,标识Data的长度
 * @param raw
 * @return
 */
public static byte[] wrapDataItemRaw(byte[] raw) {
    byte[] valid = new byte[1]; //证明此时为非法数据
    byte[] size = Parser.short2Byte((short)raw.length); //计算数据字节大小
    return Bytes.concat(valid, size, raw); //拼接DataItem 结构数据
}

/**
 * 根据给定的空间大小选择一个 PageInfo 对象。
 *
 * @param spaceSize 需要的空间大小
 * @return 一个 PageInfo 对象,其空闲空间大于或等于给定的空间大小。如果没有找到合适的 PageInfo,返回 null。
 */
public PageInfo select(int spaceSize) {
lock.lock(); // 获取锁,确保线程安全
try {
    int number = spaceSize / THRESHOLD; // 计算需要的空间大小对应的区间编号
    // 此处+1主要为了向上取整
    /*
            1、假需要存储的字节大小为5168,此时计算出来的区间号是25,但是25*204=5100显然是不满足条件的
            2、此时向上取整找到 26,而26*204=5304,是满足插入条件的
         */
    if (number < INTERVALS_NO) number++; // 如果计算出的区间编号小于总的区间数,编号加一
    while (number <= INTERVALS_NO) { // 从计算出的区间编号开始,向上寻找合适的 PageInfo
        if (lists[number].size() == 0) { // 如果当前区间没有 PageInfo,继续查找下一个区间
            number++;
            continue;
        }
        return lists[number].remove(0); // 如果当前区间有 PageInfo,返回第一个 PageInfo,并从列表中移除
    }
    return null; // 如果没有找到合适的 PageInfo,返回 null
} finally {
    lock.unlock(); // 释放锁
}
}

// 定义一个静态方法,用于创建插入日志
public static byte[] insertLog(long xid, Page pg, byte[] raw) {
    // 创建一个表示日志类型的字节数组,并设置其值为LOG_TYPE_INSERT
    byte[] logTypeRaw = {LOG_TYPE_INSERT};
    // 将事务ID转换为字节数组
    byte[] xidRaw = Parser.long2Byte(xid);
    // 将页面编号转换为字节数组
    byte[] pgnoRaw = Parser.int2Byte(pg.getPageNumber());
    // 获取页面的第一个空闲空间的偏移量,并将其转换为字节数组
    byte[] offsetRaw = Parser.short2Byte(PageX.getFSO(pg));
    // 将所有字节数组连接在一起,形成一个完整的插入日志,并返回这个日志
    return Bytes.concat(logTypeRaw, xidRaw, pgnoRaw, offsetRaw, raw);
}

// 将raw插入pg中,返回插入位置
public static short insert(Page pg, byte[] raw) {
    pg.setDirty(true); // 将pg的dirty标志设置为true,表示pg的数据已经被修改
    short offset = getFSO(pg.getData()); // 获取pg的空闲空间偏移量
    System.arraycopy(raw, 0, pg.getData(), offset, raw.length); // 将raw的数据复制到pg的数据中的offset位置
    setFSO(pg.getData(), (short) (offset + raw.length)); // 更新pg的空闲空间偏移量
    return offset; // 返回插入位置
}

3.关闭 DataManagerclose()

  • 正常关闭时,执行缓存和日志的关闭流程,并设置第一页的字节校验。
@Override
public void close() {
    super.close();
    logger.close();

    PageOne.setVcClose(pageOne);
    pageOne.release();
    pc.close();
}

DataManager的初始化
  1. 从空文件创建create()
  • 初始化 PageCacheLogger,并初始化第一页。

public static DataManager create(String path, long mem, TransactionManager tm) {
    PageCache pc = PageCache.create(path, mem);
    Logger lg = Logger.create(path);

    DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);
    dm.initPageOne();
    return dm;
}

  1. 从已有文件打开open()
  • 加载并检查第一页,必要时执行恢复操作,并填充 PageIndex

public static DataManager open(String path, long mem, TransactionManager tm) {
    PageCache pc = PageCache.open(path, mem);
    Logger lg = Logger.open(path);

    DataManagerImpl dm = new DataManagerImpl(pc, lg, tm);
    if (!dm.loadCheckPageOne()) {
        Recover.recover(tm, lg, pc);
    }
    dm.fillPageIndex();
    PageOne.setVcOpen(dm.pageOne);
    dm.pc.flushPage(dm.pageOne);

    return dm;
}

总结

DataItemDataManager 是数据库系统中数据管理的关键组件。DataItem 提供了数据的存储和访问接口,支持数据修改和事务管理。而 DataManager 负责管理底层数据的访问、缓存、事务处理和日志记录,通过这些功能的实现,为上层模块提供了安全、高效的数据操作接口。两者的协作使得数据库系统能够以高效、可靠的方式管理和操作底层数据,确保数据的安全性和一致性。

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

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

相关文章

使用root账号ssh登录虚拟机ubuntu

在C:\Users\Administrator\.ssh目录下的config中&#xff0c;添加ubuntu会在根目录中&#xff0c;建立一个root文件夹。在该文件夹中建一个.ssh目录。像免密登录ubuntu设置中&#xff0c;把公钥考进去。在vscode中打开文件夹中选择要打开的文件夹&#xff0c;就可以不需要在ubu…

fiddler抓包14_自动重定向

课程大纲 重定向&#xff08;Redirect&#xff09;&#xff1a;将网络请求重新定向到其他位置。 按重定向方式分为&#xff1a;网页重定向、域名重定向、接口重定向…… 按持续时间分为&#xff1a;永久重定向、临时重定向。 ① 永久重定向&#xff0c;HTTP 301&#xff08;Mov…

Python或R时偏移算法实现

&#x1f3af;要点 计算单变量或多变量时序距离&#xff0c;使用欧几里得、曼哈顿等函数量化不同时序差异。量化生成时序之间接近度相似性矩阵。使用高尔距离和堪培拉距离等相似度测量。实现最小方差匹配算法&#xff0c;绘制步进模式的图形表示。其他语言包算法实现。 &…

D24【 python 接口自动化学习】- python 基础之判断与循环

day24 while循环 学习日期&#xff1a;20241001 学习目标&#xff1a;判断与循环--34 while循环&#xff1a;需要多次重复执行某段程序时&#xff0c;怎么做&#xff1f; 学习笔记&#xff1a; 循环结构简介 while循环语法 代码实现 循环的退出方法 执行出错&#xff0c;异…

复合系统推文

今天推出的是复合系统协调度这个模型的工具。 参考文献:《“干线公路—城市结点”复合系统协调度分析模型》 复合系统整体协调度模型以协同学的序参量原理和役使原理为基础&#xff0c;模型的参变量选择是模型合理性的前提&#xff0c; 重点选择在整个系统发展演变过程中起主…

CSP-J模拟赛(1)补题报告

前言&#xff1a; 1.交替出场&#xff08;alter) &#xff1a;10 2.翻翻转转&#xff08;filp)&#xff1a;0 3.方格取数&#xff08;square&#xff09;&#xff1a;0 4.圆圆中的方方&#xff08;round)&#xff1a;0 总结一下&#xff1a; 第一次考&#xff0c;没爆零就是胜…

锂电池SOC估计 | Matlab基于BP神经网络的锂电池SOC估计

锂电池SOC估计 | Matlab基于BP神经网络的锂电池SOC估计 目录 锂电池SOC估计 | Matlab基于BP神经网络的锂电池SOC估计预测效果基本描述程序设计参考资料 预测效果 基本描述 锂电池SOC估计 | Matlab基于BP神经网络的锂电池SOC估计 运行环境Matlab2023b及以上。 要实现基于BP神…

鼓组编曲:鼓编写技巧之进鼓加花编写

为了方便快速查阅和运用一些教程笔记&#xff0c;个人记性有时可能不是特别好&#xff0c;所以只能疯狂做笔记了&#xff0c;制作以下图文笔记&#xff0c;仅供参考…… 鼓组加花 鼓的变动 进鼓后然后就可以动次打次了 下面是2个底鼓的加花

基于投影滤波算法的rick合成地震波滤波matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 RICK合成地震波模型 4.2 投影滤波算法原理 5.完整工程文件 1.课题概述 基于投影滤波算法的rick合成地震波滤波matlab仿真。分别通过标准的滤波投影滤波以及卷积滤波投影滤波对合成地震剖面进行滤波…

SQL第10课挑战题

1. 从OrderItems表中返回每个订单号order_num各有多少行数order_lines&#xff0c;并按order_lines对结果进行排序 2. 返回名为cheapest_item的字段&#xff0c;该字段包含每个供应商成本最低的产品&#xff08;使用products表中的prod_price)&#xff0c;然后从最低成本到最高…

Redis篇(缓存机制 - 基本介绍)(持续更新迭代)

目录 一、缓存介绍 二、经典三缓存问题 1. 缓存穿透 1.1. 简介 1.2. 解决方案 1.3. 总结 2. 缓存雪崩 2.1. 简介 2.2. 解决方案 2.3. 总结 3. 缓存击穿 3.1. 简介 3.2. 解决方案 3.3. 总结 4. 经典三缓存问题出现的根本原因 三、常见双缓存方案 1. 缓存预热 1…

MQ高级:RabbitMQ小细节

在之前的学习中&#xff0c;我们只介绍了消息的发送&#xff0c;但是没有考虑到异常的情况&#xff0c;今天我们就介绍一些异常情况&#xff0c;和细节的部分。 目录 生产者可靠性 生产者重连 生产者确认 MQ可靠性 持久化 Lazy Queue 消费者可靠性 消费者确认机制 失…

LoadRunner实战测试解析:记录一次性能测试过程

环境准备 PC&#xff1a; Windows7/XP LoadRunner11&#xff1a; 与win10及以上版本不兼容 Nmon&#xff1a; 性能监控工具&#xff0c;部署到被测服务器 LoadRunner破解安装 下载地址&#xff1a;https://pan.baidu.com/s/1WJjcFWhrkWW-GgYwXdEniQ 提取码&#xff1a;f4z…

基于Spark的汽车行业大数据分析及可视化系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

NetApp 混合闪存 FAS 统一存储平台

挑战 简化内部和公有云中的数据管理 各种规模的企业在精简其存储运维方面正面临越来越大的挑战。他们存储和备份的数据量不断增长&#xff0c;而预算却在缩减。他们需要一个既能满足内部环境要求&#xff0c;又能结合公有云战略的解决方案。 解决方案 兼顾容量与性能的存储&…

设计模式-策略模式-200

优点&#xff1a;用来消除 if-else、switch 等多重判断的代码&#xff0c;消除 if-else、switch 多重判断 可以有效应对代码的复杂性。 缺点&#xff1a;会增加类的数量&#xff0c;有的时候没必要为了消除几个if-else而增加很多类&#xff0c;尤其是那些类型又长又臭的 原始代…

scratch棒球运动 2024年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试一级真题和答案解析

目录 scratch棒球运动 一、题目要求 1、准备工作 2、功能实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、 推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、…

进程--信号量

信号量是什么 资源的竞争 资源竞争 : 当多个进程同时访问共享资源时&#xff0c;会产生资源竞争&#xff0c;最终最导致数据混乱临界资源 : 不允许同时有多个进程访问的资源&#xff0c;包括硬件资源(CPU、内存、存储器以及其他外围设备)与软件资源(共享代码段、共享数据结构…

SpringCloudEureka实战:搭建EurekaServer

1、依赖引入 <dependencies><!-- 注册中心 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency> </dependencies> <de…

66.对cplusplus网的strtok函数的详细解释(补第56篇的翻译)

56.【C语言】字符函数和字符串函数(strtok函数) 点我跳转 目录 56.【C语言】字符函数和字符串函数(strtok函数) 点我跳转 1.原文 2. 翻译 1.原文 原文链接: cplusplus的介绍 点我跳转 2. 翻译 函数 strtok char * strtok ( char * str, const char * delimiters ); Spli…