【操作系统】操作系统IO技术底层机制和ZeroCopy

news2024/12/23 23:57:23

1.DMA技术详解

(1)应用程序 从 磁盘读写数据 的时序图(未用DMA技术前)

在这里插入图片描述

(2)什么是DMA 技术 (Direct Memory Access)

  • 直接内存访问,直接内存访问是计算机科学中的一种内存访问技术。
  • DMA之前:要把外设的数据读入内存或把内存的数据传送到外设,一般都要通过CPU控制完成,利用中断技术。
  • 允许某些硬件系统能够独立于CPU直接读写操作系统的内存,不需要中处理器(CPU)介入处理。
  • 数据传输操作在一个DM控制器(DMAC)的控制下进行,在传输过程中CPU可以继续进行其他的工作。
  • 在大部分时间CPU和I/O操作都处于并行状态,系统的效率更高。

在这里插入图片描述

(3)应用程序的读写数据

在这里插入图片描述

  • 读本地磁盘

    • 操作系统检查内存缓冲区读取,如果存在则直接把内核空间的数据copy到用户空间(CPU负责),应用程序即可使用。

    • 上步没数据,则从磁盘中读取到内核缓冲(DMA负责),再把内核空间的数据copy到用户空间(CPU负责),应用程序即可使用

    • 硬盘->内核缓冲区->用户缓冲区

  • 写操作本地磁盘

    • 根据操作系统的写入方式不一样,buffer IO 和 direct IO ,写入磁盘时机不一样。
    • buffer IO
      • 应用程序把数据从用户空间copy到内核空间的缓冲区(CPU负责),再把内核缓冲区的数据写到磁盘(DMA负责)。
    • direct IO
      • 应用程序把数据直接从用户态地址空间写入到磁盘中,直接跳过内核空间缓冲区。
      • 减少操作系统缓冲区和用户地址空间的拷贝次数,降低了CPU和内存开销。
    • 用户缓冲区->内核缓冲区->硬盘
  • 读网络数据

    • 网卡Socket(类似磁盘)中读取客户端发送的数据到内核空间(DMA负责)。
    • 把内核空间的数据copy到用户空间(CPU负责),然后应用程序即可使用。
  • 写网络数据

    • 用户缓冲区中的数据copy到内核缓冲区的Socket Buffer 中(CPU负责)
    • 将内核空间中的Socket Buffer 拷贝到Socket协议栈(网卡设备)进行传输(DMA负责)

(4)DMA的工作总结

  • 从磁盘的缓冲区到内核缓冲区的拷贝工作。
  • 从网卡设备到内核的socket buffer 的拷贝工作。
  • 从内核缓冲区到磁盘缓冲区的拷贝工作。
  • 从内核的socket buffer到网卡设备的拷贝工作。
  • 注意:内核缓冲区到用户缓冲区之间的拷贝工作仍然由CPU负责

(5)DMA技术带来的性能损耗

在这里插入图片描述

  • 上图应用程序从磁盘读取数据发送到网络上的损耗,程序需要两个命令 先read读取,再write写出
  • 四次内核态和用户态的切换
  • 四次缓冲区的拷贝(2次DMA拷贝、2次CPU拷贝)
    • 读取:磁盘缓冲区到内核缓冲区(DMA)
    • 读取:内核缓冲区到用户缓冲区(CPU)
    • 写出:用户缓冲区到内核缓冲区Socket Buffer(CPU)
    • 写出:内核缓冲区的Socket Buffer到网卡设备(DMA)

为了解决这种性能的损耗所以就诞生了零拷贝。

2.ZeroCopy零拷贝技术简介

(1)什么是零拷贝ZeroCopy

​ 减少不必要的内核缓冲区跟用户缓冲区之间的拷贝工作,从而减少CPU的开销和减少kernel和user模式的上下文切换,达到性能的提升。从磁盘中读取文件通过网络发送出去,只需要拷贝2\3次和2\4的内核态和用户态的切换即可。

ZeroCopy技术实现方式有两种(内核态和用户态切换次数不一样)

  • 方式一:mmap+write
  • 方式二:sendfile

(2)ZeroCopy的实现底层 mmap + write

  • 操作系统都使用虚拟内存,虚拟地址通过多级页表映射物理地址。

  • 多个虚拟内存可以指向同一个物理地址,虚拟内存的总空间远大于物理内存空间。

  • 如果把内核空间和用户空间的虚拟地址映射到同一个物理地址,就不需要来回复制数据。

  • mmap系统调用函数会直接把内核缓冲区的数据映射到用户空间,内核空间和用户空间就不需要在进行数据拷贝的操作了,节省了CPU开销。

  • mmap()负责读取,write()负责写出

  • 执行流程

    • 应用程序先调用mmap()方法,将数据从磁盘拷贝到内核缓冲区,返回结束(DMA负责)。在调用write(),内核缓冲区的数据直接拷贝到内核socket buffer (CPU负责),然后把内核缓冲区的Socket Buffer 给直接拷贝给Socket协议线,即网卡设备中,返回结束(DMA负责)

在这里插入图片描述

  • 采用mmap之后,CPU用户态和内核态上下文切换依旧是4次和全程有3次数据拷贝
  • 2次DMA拷贝、1次CPU拷贝、4次内核态用户态切换,减少了1次CPU拷贝

(3)ZeroCopy的实现底层 sendfile

  • Linux kernal 2.1新增发送文件的系统调用函数sendfile()。
  • 执行流程
    • 替代read()和write()两个系统调用,减少一次系统调用,即减少2次CPU上下文切换的开销,调用sendfile(),从磁盘读取到内核缓冲区,然后直接把内核缓冲区的数据拷贝到socket buffer缓冲区里,再把内核缓冲区的SocketBuffer给直接拷贝给Socket协议栈,即网卡设备中(DMA负责)。

在这里插入图片描述

  • 采用sendfile后,CPU用户态和内核态上下文切换是2次 和 全程3次的数据拷贝,2次DMA拷贝、1次的CPU拷贝、2次内核态用户态切换。
  • Linux 2.4+ 版本之后改进sendfile,利用DMA Gather(带有收集功能的DMA),变成了真正的零拷贝(没有CPU Copy)
    • 应用程序先调用sendfile()方法,将数据从磁盘拷贝到内核缓冲区(DMA负责)

    • 把内存地址、偏移量的缓冲区fd描述符拷贝到Socket Buffer中去 拷贝很少的数据,可忽略

      • 本质和虚拟内存的解决方法思路一样,就是内存地址的记录
    • 然后把内核缓冲区的Socket Buffer给直接拷贝给Socket协议栈 即网卡设备中,返回结束(DMA负责)

在这里插入图片描述

3.Java和主流中间件里的零拷贝技术

(1)Java中有哪些零拷贝技术

  • Java NIO对mmap的实现 fileChannel.map()
  • Java NIO对sendfile的实现 fileChannel.transferTo()fileChannel.transferFrom()

(2)什么是FileChannel

  • FileChannel是一个连接到文件的通道,可以通过文件通道读写文件,该常被用于搞笑的网络/文件的数据传输和大文件拷贝
  • 应用程序使用FileChannel写完以后,数据是在PageCache上的,操作系统不定时的把PageCache的数据写入到磁盘。为了避免宕机数据丢失,使用channel.force(true) 把文件相关的数据强制刷入磁盘上去。
  • 使用之前必须先打开它,但是无法直接new一个FileChannel。
  • 常规通过使用一个InputStream、OutputStream或者RandomAccessFile来获取一个FileChannel实例。
RandomAccessFile randomAccessFile = new RandomAccessFile("文件路径","rw");
FileChannel inChannel = randomAccessFile.getChannel();

(3)mmap方式实现

  • map方法,把文件映射成内存映射文件
  • MappedByteBuffer,是抽象类也是ByteBuffer的子类,具体实现子类是DirectByteBuffer,可被通道进行读写。
  • 一次map大小要限制在2G内,过大map会增加虚拟内存回收和重新分配的压力,直接报错。
  • FileChannel.java中的map对long size 进行了限制,不能大于Integer.MAX_VALUE,否则就报错

在这里插入图片描述

  • JDK层做限制是因为底层C++的类型,无符号int类型最大是2^31 -1, 2^31 -1 字节就是 2GB - 1B。
MappedByteBuffer map(int mode,long position,long size)
position:文件开始位置
size:映射文件区域大小
mode:访问该内存映射文件的方式,READ_ONLY(只读)、READ_WRITE(读写)、PRIVATE(创建一个读写副本)

(4)sendfile方式实现

  • fileChannel.transferTo(long postition,long count,WritableByteChannel target)
  • 将字节从此通道的文件传输到给定的可写入字节通道。
  • 返回值为真实拷贝的size,最大拷贝2G,超出2G的部分将丢弃。
position:文件中的位置,从此位置开始传输,必须非负数
count:要传输的最大字节数,必须非负数
target:目标通道
返回:实际已传输的字节数,可能为零
  • fileChannel.transferFrom(ReadableByteChannel src, long position, long count)
  • 将字节从给定的可读取字节通道传输到此通道的文件中
  • 对比 从源通道读取并将内容写入此通道的循环语句相比,此方法更高效
src:源通道
position:文件中的位置,从此位置开始传输,必须非负数
count:要传输的最大字节数, 必须非负数
返回:实际已传输的字节数,可能为零
  • transferFrom允许将一个通道连接到另一个通道,不需要在用户态和内核态来回复制,同时通道的内核态数据也无需复制,transferTo只有源为FileChannel才支持transfer这种搞笑的复制方式,其他如SocketChannel都不支持transfer模式。
  • 一般可以做FileChannel->FileChannel->FileChannel 和 FileChannel->SocketChannel的transfer零拷贝

4.文件IO性能对比实战

实现一个文件拷贝,对比不同IO方式性能差异,文件大小 200MB~5GB

编码实现:

  • 普通java的io流
  • 普通java的带buffer的io
  • 零拷贝实现之mmap的io
  • 零拷贝实现之sendfile的io

运行环境准备

  • Linux CentOS7.X
  • 安装JDK11 配置全局环境变量 vi /etc/profile
JAVA_HOME=/usr/local/jdk11
CLASSPATH=$JAVA_HOME/lib/
PATH=$PATH:$JAVA_HOME/bin
export PATH JAVA_HOME CLASSPATH
  • 环境变量立刻生效

    • source /etc/profile
  • 查看安装情况 java -version

在这里插入图片描述

  • 准备1.34G测试文件

在这里插入图片描述

(1)普通java的io验证

public class IOTest {

    public static void main(String[] args) {
        String inputFilePath = args[0];
        String outputFilePath = args[1];

       	long start = System.currentTimeMillis();
        try (
                FileInputStream fis = new FileInputStream(inputFilePath);
                FileOutputStream fos = new FileOutputStream(outputFilePath)
        ) {
            byte[] buf = new byte[1];
            while(fis.read(buf) != -1){
                fos.write(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("普通IO耗时:"+(end-start));
    }
测试:java IOTest.java "/usr/local/music.zip" "/usr/local/io-music.zip"

在这里插入图片描述

(2)普通java的带buffer的io

public class BufferIOTest {
    public static void main(String[] args) {
        String inputFilePath = args[0];
        String outputFilePath = args[1];
        long start = System.currentTimeMillis();
        try (
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inputFilePath));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFilePath))
        ) {
            byte[] buf = new byte[1];
            while(bis.read(buf) != -1){
                bos.write(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("Buffer IO耗时:"+(end-start));
    }
}

在这里插入图片描述

(3)零拷贝实现之mmap的io

  • 一次 map 最大支持2GB,超过2GB会报错
public class MmapIOTest {
    public static void main(String[] args) {

        String inputFilePathStr = args[0];
        String outputFilePathStr = args[1];

        long start = System.currentTimeMillis();
        try (
                FileChannel channelIn = new FileInputStream(inputFilePathStr).getChannel();
                FileChannel channelOut = new RandomAccessFile(outputFilePathStr, "rw").getChannel()
        ) {
            long size = channelIn.size();
            System.out.println("mappedFile:"+size);
            MappedByteBuffer mbbi = channelIn.map(FileChannel.MapMode.READ_ONLY, 0, size);
            MappedByteBuffer mbbo = channelOut.map(FileChannel.MapMode.READ_WRITE, 0, size);
            for (int i = 0; i < size; i++) {
                byte b = mbbi.get(i);
                mbbo.put(i,b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("mmap 零拷贝 IO 耗时:"+(end-start));
    }
}

在这里插入图片描述

(4)零拷贝实现之sendfile的io

  • 最大拷贝2G,超出2G的部分将丢弃,最终拷贝的文件大小只有2GB多点,超过2GB可以考虑多次执行
public class SendFileIOTest {
    public static void main(String[] args) {

        String inputFilePathStr = args[0];
        String outputFilePathStr = args[1];

        long start = System.currentTimeMillis();
        try (
                FileChannel channelIn = new FileInputStream(inputFilePathStr).getChannel();
                FileChannel channelOut = new FileOutputStream(outputFilePathStr).getChannel()
        ) {
            // 代码一:针对小于2GB的问题,返回值为真实拷贝的size,最大拷贝2G,超出2G的部分将丢弃,最终拷贝文件大小只有2GB
            //channelIn.transferTo(0,channelIn.size(),channelOut);

            // 代码二:针对大于2GB的文件
            long size = channelIn.size();
            for (long left = size;left>0;){
                //transferSize所拷贝过去的真实长度,size - left 计算出下次要拷贝的位置
                long transferSize = channelIn.transferTo((size - left),left,channelOut);
                System.out.println("总大小:"+size+",拷贝大小:"+transferSize);
                //left剩余字节多少
                left = left - transferSize;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("sendfile 零拷贝 IO 耗时:"+(end-start));
    }
}

在这里插入图片描述

(5)测试结果分析

  • 1~2GB的文件
  • 普通拷贝
    • 普通java的io流【慢】3973924秒
    • 普通java的带buffer的io【快】33196秒
  • 零拷贝
    • 零拷贝实现之mmap的io【快】7131秒
    • 零拷贝实现之sendfile的io【快】1784秒
  • 分析原因之前,我们先来了解一下局部性原理
局部性原理:指计算机在执行某个程序时,倾向于使用最近使用的数据
时间局部性:如果程序中的某条指令一旦被执行,则不久的将来该指令可能再次被执行
空间局部性:一旦程序访问了某个存储单元,在不久的将来附近的存储单元也有可能被访问
  • 普通的IO和Buffer IO,为什么带有Buffer的IO要比普通的IO性能高?
每次读取数据的时候,系统根据局部性原理,通过DMA会读入更多的数据到内核缓冲区里面
OS根据局部性原理会在一次read(),系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中
当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程缓冲区,避免了再次的抵消磁盘IO操作
OS已经帮减少磁盘IO操作次数,提高了性能
  • 两种零拷贝的方式对比
(1)sendfile

无法在调用过程中修改数据,只适用于应用程序不需要对所访问数据进行处理修改情况,适合静态文件传输,MQ的Broker发送消息给消费者。适合大文件传输,2次上下文切换,最少2次数据拷贝。

(2)mmap

在mmap调用可以在应用程序中直接修改Page Cache中的数据,使用的是mmap+write两步。调用比sendfile成本高,但由于传统的拷贝方式,适用于多个线程以只读的方式同时访问同一个文件,mmap机制下多线程共享同一个物理内存空间,节约内存。适合小数据量续写,4次上下文切换,3次数据拷贝。

5.主流中间件中用到的ZeroCopy技术

(1)Nginx使用的是sendfile 零拷贝

  • WebServer处理静态页面请求时,是从磁盘中读取网页的内容,因为sendfile不能在应用程序中修改数据,所以最适合静态文件服务器或者是直接转发数据的代理服务器。

(2)rocketmq主要是mmap,也有小部分使用sendfile

  • rocketMQ在消息存盘和网络发送使用mmap, 单个CommitLog文件大小默认1GB
    • 要在用户进程内处理数据,然后再发送出去的话,用户空间和内核空间的数据传输就是不可避免的

(3)Kafka主要是sendfile,也有小部分使用mmap

  • kafka 在客户端和 broker 进行数据传输时,broker 使用 sendfile 系统调用,类似 【FileChannel.transferTo】 API,将磁盘文件读到 OS 内核缓冲区后,直接转到 socket buffer 进行网络发送,即 Linux 的 sendfile。
    中读取网页的内容,因为sendfile不能在应用程序中修改数据,所以最适合静态文件服务器或者是直接转发数据的代理服务器。

(2)rocketmq主要是mmap,也有小部分使用sendfile

  • rocketMQ在消息存盘和网络发送使用mmap, 单个CommitLog文件大小默认1GB
    • 要在用户进程内处理数据,然后再发送出去的话,用户空间和内核空间的数据传输就是不可避免的

(3)Kafka主要是sendfile,也有小部分使用mmap

  • kafka 在客户端和 broker 进行数据传输时,broker 使用 sendfile 系统调用,类似 【FileChannel.transferTo】 API,将磁盘文件读到 OS 内核缓冲区后,直接转到 socket buffer 进行网络发送,即 Linux 的 sendfile。

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

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

相关文章

设计模式之美总结(开源实战篇)

title: 设计模式之美总结&#xff08;开源实战篇&#xff09; date: 2023-01-10 17:13:05 tags: 设计模式 categories:设计模式 cover: https://cover.png feature: false 文章目录1. Java JDK 应用到的设计模式1.1 工厂模式在 Calendar 类中的应用1.2 建造者模式在 Calendar …

Pyton接口自动化相关【易报错问题及解决方法】

Pyton接口自动化相关【易报错问题及解决方法】 目录&#xff1a;导读 Python怎么链接数据库 python 链接数据库时报错 TypeError: %d format: a number is required, not str 是因为端口号写成字符串格式的了 python exists判断文件是否存在 pycharm下查看日志文件中文乱码…

【数据挖掘实战】——电力窃漏电用户自动识别

【数据挖掘实战】——电力窃漏电用户自动识别一、背景和挖掘目标二、分析方法与过程1、初步分析2、数据抽取3、探索分析4、数据预处理5、构建专家样本三、构建模型1、构建窃漏电用户识别模型2、模型评价3、进行窃漏电诊断拓展思考项目代码地址&#xff1a;https://gitee.com/li…

LaoCat带你认识容器与镜像(实践篇二下)

实践篇主要以各容器的挂载和附加命令为主。 本章内容 本文实操全部基于Ubuntu 20.04 宿主机 > linux服务器本身 Docker > 20.10.22 接上章内容&#xff0c;接下来该章围绕Docker安装并运行之RabbitMQ、ElasticSearch&#xff0c;大部分命令来源于DockerHub官网&#xf…

Apache日志分析器

您的Apache HTTP服务器生成的日志数据是信息的宝库。使用这些信息&#xff0c;您可以判断您服务器的使用情况、找出漏洞所在&#xff0c;并设法改进服务器结构和整体性能。审核您的Apache日志可在以下情况派上用场&#xff0c;其中包括&#xff1a;识别和纠正频繁出现的错误以增…

github相关

1.本地项目上传到远程github 1.1github上创建项目 1)github上创建项目 2)复制地址 1.2上传自己的项目 1)本地项目目录下执行 git init “git init”命令用于创建git仓库&#xff0c;其可以在一个已有的非git项目的根目录下执行&#xff0c;把已有项目初始化成为git仓库&…

微服务实战--高级篇:多级缓存:Nginx本地缓存、Redis、Tomcat、JVM缓存、数据库

多级缓存 1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; 请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时…

模型部署综述

https://github.com/leeguandong/KuaiZaihttps://github.com/leeguandong/KuaiZaiAI 框架部署方案之模型部署概述 - 知乎文小P 家的 1011010 概述模型训练重点关注的是如何通过训练策略来得到一个性能更好的模型&#xff0c;其过程似乎包含着各种“玄学”&#xff0c;被戏称为“…

全局异常处理--Java实战项目篇

系列文章目录 Java后端开发功能模块思路 Spring Boot自动配置–如何切换内置Web服务器 Spring Boot读取配置文件内容的三种方式 该系列文章持续更新&#xff0c;更多的文章请点击我的主页查看哦&#xff01; 文章目录系列文章目录前言一、出现的问题二、解决问题的方法1. 添加…

iPhone更新iOS 16.3出现应用卡死、闪退的问题怎么办?

在升级最新的 iOS 16.3 系统后&#xff0c;有些用户可能遇到了个别应用无法正常打开&#xff0c;卡死的异常情况。大家可以尝试通过如下方式解决问题。 1.重新启动应用&#xff1a; 如果应用出现卡死或闪退&#xff0c;可从 iPhone 屏幕由底往上滑&#xff08;或连续按两次 H…

Java变量和数据类型,超详细整理,适合新手入门

目录 一、什么是变量&#xff1f; 二、变量 变量值互换 三、基本数据类型 1、八种基本数据类型 2、布尔值 3、字符串 四、从控制台输入 一、什么是变量&#xff1f; 变量是一种存储值的容器&#xff0c;它可以在程序的不同部分之间共享&#xff1b;变量可以存储数字、字…

C语言进阶——通讯录模拟实现

&#x1f307;个人主页&#xff1a;_麦麦_ &#x1f4da;今日名言&#xff1a;只有走在路上&#xff0c;才能摆脱局限&#xff0c;摆脱执着&#xff0c;让所有的选择&#xff0c;探寻&#xff0c;猜测&#xff0c;想象都生机勃勃。——余秋雨《文化苦旅》 目录 一、前言 二、正…

让我百思不得其解的infer究竟是怎么推导类型的?

情景再现 有这么一个条件类型的基本语法: T extends U ? X : Y; 如果占位符类型U是一个可以被分解成几个部分的类型&#xff0c;譬如数组类型&#xff0c;元组类型&#xff0c;函数类型&#xff0c;字符串字面量类型等。这时候就可以通过infer来获取U类型中某个部分的类型。 …

95后外贸SOHO,年入7位数,他究竟是怎么做的?

外贸SOHO&#xff0c;一年到底能挣多少钱&#xff1f;有人说&#xff1a;“勤勤恳恳&#xff0c;年薪也就十来万吧”&#xff1b;也有人说&#xff1a;“100万而已我早就已经挣到了”&#xff1b;还有人说&#xff1a;“谁说新手难出头&#xff1f;我做跨境半年赚200万&#xf…

Linux设备驱动移植

目录 一、设备树 1.1设备树 1.2设备树文件 1.3设备树语法 1.4Linux内核驱动移植 二、网卡驱动 2.1在make menuconfig界面中选中要安装的驱动 2.2在设备树中添加/修改相应的设备信息 2.3修改时钟 2.4修改eMMc 2.5编译测试 一、设备树 1.1设备树 设备树是一种描述硬…

MATLAB 粒子群算法

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

Cy5 Alkyne,1223357-57-0,花青素Cyanine5炔基,氰基5炔烃

CAS号&#xff1a;1223357-57-0 | 英文名&#xff1a; Cyanine5 alkyne&#xff0c;Cy5 Alkyne | 中文名&#xff1a;花青素CY5炔基CASNumber&#xff1a;1223357-57-0Molecular formula&#xff1a;C35H42ClN3OMolecular weight&#xff1a;556.19Purity&#xff1a;95%Appear…

全网详解MyBatis-Plus updateById方法更新不了空字符串或null的解决方法

文章目录1. 文章引言2. 分析问题3. 解决问题3.1 方法1&#xff1a;全局配置方式3.2 方法2&#xff1a;非null字段验证策略3.3 方法3&#xff1a;通过注解的方式4. 总结1. 文章引言 在开发的过程中&#xff0c;我们经常使用MyBatis-Plus的updateById方法更新数据表&#xff0c;…

ChatGPT学习心得一(使用node+react做了一个案例)

项目地址 http://chat.xutongbao.top 项目截图 使用技术栈 nodeSQLiteredisnginxlog4jsexpressjenkinscdnreactantdreact-scrollbars-customiconfontwebpackpostmanaxiosreduximmutablenpmyarnopenai等等 官网 https://openai.com/blog/chatgpt/ 官方聊天应用 https://ch…

【Linux】多线程编程 - 同步/条件变量/信号量

目录 一.线程同步 1.什么是线程同步 2.为什么需要线程同步 3.如何实现线程同步 二.条件变量 1.常见接口以及使用 2.wiat/signal中的第二个参数mutex的意义 3.代码验证 三.POSIX信号量 1.概念 2.常见接口以及使用 四.条件变量vsPOSIX信号量 一.线程同步 1.什么是线…