Linux --- 高级IO

news2024/11/28 14:44:16

目录

1. 什么是IO

2. 阻塞的本质

3. 五种IO模型

3.1. 通过故事认识五种IO模型

3.2. 上述故事的总结

3.3. 具体的五种IO模型

3.3.1. 阻塞IO

3.3.2. 非阻塞轮询式IO

3.3.3. 信号驱动IO

3.3.4. 多路转接IO

3.3.5. 异步IO

4. 非阻塞IO

4.1. fcntl 系统调用


1. 什么是IO

冯诺依曼体系:

站在冯诺依曼体系的视角,从输入设备读取数据到存储器,这个过程就是Input;而将存储器的数据写入到输出设备,这个过程就是Output;

因此,IO本质上就是访问外设的过程。

因为外设相较于内存、cache缓存、寄存器、CPU的速率是比较低的,故IO的效率是比较低的,尤其涉及到网络,效率问题就更加突出。

2. 阻塞的本质

IO过程的低效,我们可以用读取数据为例:

当进程 read/recv 时,如果底层缓冲区没有数据,read/recv 会被阻塞;

当进程 read/recv 时,如果底层缓冲区有数据, read/recv 会将数据从内核缓冲区拷贝到应用层;

阻塞的本质:

  • 站在操作系统的视角: 将该进程的PCB放在等待队列中;
  • 站在进程自身的视角: 本质上就是让我这个进程等待;

因此,当进程等了 (等待事件就绪),数据就绪后,再进行数据拷贝,这就是一次IO过程;

故我们认为,IO = 等待 (事件就绪) + 数据拷贝

因此, read、recv、write、send 等,本质上都是先等待IO类事件就绪,在进行数据拷贝 (内核将数据拷贝给用户或者用户将数据拷贝给内核);

那么什么叫做低效的IO呢?

根据 IO = 等待 (事件就绪) + 数据拷贝,我们发现,单位时间,只要等待的比重越高,那么这个IO过程就越低效;

因此,那什么叫做高效的IO呢?即如何提高IO效率?

在单位时间,让等待的比重变得越低,那么IO的效率就变得越高,因此,高效IO的本质:降低IO过程中等待的比重,提高单位时间内拷贝数据的量;

3. 五种IO模型

3.1. 通过故事认识五种IO模型

通过一个钓鱼故事,来认识这五种IO模型:

今天,我们对钓鱼的过程进行简化一下(不要考虑什么打窝的事情了😄😄😄),我们认为钓鱼就分两步:

  • step 1: 等待鱼上钩, 等待事件就绪;
  • step 2: 鱼上钩后,把鱼钓起来, 数据拷贝。  

根据上面对IO的简单理解,类比到钓鱼过程中,什么情况下,一个人钓鱼的效率非常高呢?

  • 钓鱼 = 等待 + 钓起来;
  • 因此,只要单位时间等待的比重非常低,那么这个人钓鱼的效率一定非常高。

下面我们就通过一个故事,来认识下五种IO模型:

张三是一个钓鱼爱好者,带着帽子、墨镜、马扎,就来到鱼塘边,在钓鱼过程中:

张三死死的盯着鱼漂,其他事情都不做,鱼漂不动,他也不动,过了一会,鱼漂动了,张三就将鱼钓上来,这是张三;

李四是张三的老朋友,路过鱼塘时,看到张三在钓鱼,自己也拿着鱼竿去钓鱼了,在钓鱼过程中:

李四一会儿刷下手机,一会儿和张三聊天 ( 当然张三没理他 ),一会儿又盯着鱼漂,反正一直没闲着,过了一会儿,鱼漂动了,他抬头看了一眼,就将鱼钓了起来,这是李四;

王五也是一个钓鱼爱好者,路过鱼塘,也拿着鱼竿跑过来了,王五与前两者相比,多做了一步,他在鱼漂的位置挂了一个铃铛🔔,只要鱼漂一动,铃铛就会响,在钓鱼过程中:

王五一会儿看下张三、一会儿又和李四闲聊、一会儿又刷手机,在整个钓鱼过程中,反正王五就是不看鱼漂,过了一会儿,铃铛🔔响了,他头都不抬,直接收杆,将鱼钓起来了,这是王五;

赵六家是卖鱼竿的,路过鱼塘时,也想钓鱼,就从家里拿了100只鱼竿,将这些鱼竿全都用上,在钓鱼过程中:

因为挂了100只鱼竿,一会儿这边的鱼漂动了,一会儿那边的鱼漂动了,所以赵六就来回的跑,陆陆续续的鱼被钓上来了,这是赵六;

田七作为全村的首富,有一个司机叫小吴,这天,田七坐着豪华轿车路过鱼塘,看到鱼塘边的四个奇葩,一个一动不动,像个石头一样;一个像是多动症一样的;一个一直不看鱼漂;一个挂了密密麻麻的鱼竿,来回跑的汉子;

田七虽然不是非常想钓鱼,但是他却想吃鱼,因此对小吴说,咱去钓鱼,但是小吴说,不行,老板,你要去公司开会,不能钓鱼;田七想了想,行,这样,你帮我去钓鱼,我自己去公司,鱼钓上后,你给我打电话,我再过来;于是,小吴就去帮田七钓鱼去了,田七自己开车去公司开会了,这是田七;

3.2. 上述故事的总结

张三的钓鱼方式:阻塞式;

李四的钓鱼方式:非阻塞轮询式;

王五的钓鱼方式:信号驱动;

赵六的钓鱼方式:多路转接 (或多路复用);

田七的钓鱼方式:异步IO;

这五种方式,我们称之为五种IO模型;

谁钓鱼最高效呢?为什么?

赵六钓鱼是最高效的,因为:

  • 站在鱼🐟的角度,鱼🐟正在水里游哉悠哉的游着,抬头一看,看到104个食物 (诱饵) 在我的眼前,假设鱼🐟咬任何一个食物 (诱饵) 是等概率的,那么如果此时鱼🐟咬钩了,这个诱饵有 100/104,即25/26的概率是赵六的鱼饵;
  • 站在钓鱼者的角度,因为赵六的鱼竿很多,所以鱼🐟咬钩有很大概率咬的是赵六的鱼竿,所以赵六有很大概率钓上鱼,故在单位时间内,赵六等待的比重是非常低的,因此,赵六钓鱼的效率是非常高的。

只要一个执行流 (进程、线程) 参与了IO过程,我们就称之为同步IO;

IO的过程分两步:

  • 等待事件就绪;
  • 拷贝数据。

因此只要执行流参与了上述的任何一步、或者两者都参与了,那么我们就称之为同步IO;

故,在上面的五种IO模型中,前四种 (阻塞式、非阻塞轮询式、信号驱动、多路转接) 我们都称之为同步IO;

而对于最后一种,即田七的钓鱼方式而言,他既没有等待鱼🐟咬钩 (等待事件就绪),也没有钓起鱼🐟 (拷贝数据),故我们将这种IO方式,称之为异步IO;

王五的信号驱动算同步IO吗?

  • 首先,王五的信号驱动是同步IO, 可是,我们知道,信号的产生是异步的,这如何解释呢?
  • 因为IO = 等待事件就绪 + 拷贝数据, 虽然王五在等待过程中,可以做其他事情,但是一旦鱼咬钩了,王五是会将其钓上来的,换言之,当底层缓冲区有数据后,王五会进行数据拷贝,即王五是会参与IO过程的,故信号驱动这种方式也属于同步IO;
  • 虽然信号产生的确是异步的,但是当信号产生之后,信号驱动是要参与IO过程的,故信号驱动属于同步IO;
  • 换言之,我们认为,只要一个执行流参与了IO过程 (等待事件就绪 + 拷贝数据),我们就认为它是一个同步IO;
  • 如果一个执行流在整个IO过程都没有参与,完全脱离,那么就是异步IO;

阻塞IO和非阻塞轮询式IO,它们的区别是什么呢?

首先,阻塞IO和非阻塞轮询式IO都属于同步IO,因为它们都要参与IO过程 (等待数据就绪 + 拷贝数据);

其次,阻塞IO和非阻塞轮询式IO的主要区别就在于:等待数据就绪,这个等的比重不一样罢了,前者阻塞等待,后者非阻塞等待;

我们是学习过系统知识的,IO是谁在IO呢? 当然是执行流在IO;

因此,阻塞式IO,我们可以理解为执行流去检测某个文件描述符上是否有事件就绪,如果没有就绪,执行流就阻塞等待,等待事件就绪;

那什么是阻塞呢?

  • 站在操作系统的视角,就是把该执行流的PCB的状态由R -> !R状态,比如S状态,并将该PCB链入到某个等待队列中,这个队列一般都是与该执行流所等待的文件描述符相匹配的;
  • 此时这个执行流就被挂起阻塞了,后续就需要操作系统帮助处理了,比如操作系统识别到某个事件就绪,那么操作系统将在该文件描述符下等待的相关执行流唤醒,状态更改为R状态,并将PCB链入到运行队列中,此时这个执行流不就可以继续被调度,拷贝数据了吗?

多提一嘴,一般而言,执行流在等待什么,什么就需要提供相关队列,或者其他数据结构;

  • 比如,执行流等待某个条件变量,那么条件变量需要自身提供一个等待队列;
  • 再比如,执行流等待某个文件描述符,那么该文件描述符也需要提供一个等待队列。

那么什么是非阻塞呢?

  • 非阻塞,就是不阻塞啊,站在操作系统的视角,如果一个执行流检测某个文件描述符上的事件不就绪时,那么操作系统不会去更改这个执行流的状态,也不会将它的PCB链入到等待队列中,换言之,此时,操作系统并不关心,也不处理;
  • 因此,在非阻塞情况下,执行流不会被阻塞,故它可以在整个IO过程中不断的检测事件是否就绪,如果不就绪,可以处理其他任务,并稍后在进行检测,而这种模式不就是轮询过程吗?

不知道各位有这样的疑惑吗? 线程同步和同步IO这两个有关系吗?

  1. 先说答案, 毫无关系;
  2. 线程同步:在多线程场景下,多执行流协同工作时,为了解决访问临界资源合理性的问题,让执行流可以按照特定的顺序访问临界资源,我们称之为线程同步;
  3. 同步IO:一个执行流在进行IO时,如果参与了IO过程 (等待事件就绪或者拷贝数据),我们就认为它是同步IO;
  4. 线程同步是在多线程场景下,多执行流进行协同工作时,才会谈论的;
  5. 同步IO是在IO过程中,才会谈论的;
  6. 可见,线程同步和同步IO的应用场景都不相同,因此,这两者毫无关联。

3.3. 具体的五种IO模型

3.3.1. 阻塞IO

阻塞IO: 在内核将数据就绪之前,系统调用会一直等待 (阻塞等待),所有的文件描述符或者套接字,默认都是阻塞方式;

3.3.2. 非阻塞轮询式IO

非阻塞轮询式IO:如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回 EWOULDBLOCK (Error Would Block)错误码;

非阻塞IO往往需要以循环的方式反复读写文件描述符,这个过程称之为轮询,非常消耗CPU的资源,一般只会在特定场景下使用。

3.3.3. 信号驱动IO

内核将数据准备好的时候,使用SIGIO信号通知执行流进行IO操作。 

从下图我们也可以看出,信号驱动这个等,并不是等待信号产生 (信号产生是异步的),而是等待数据,数据就绪后,内核会向执行流发送信号 (SIGIO),应用程序在拷贝数据; 

3.3.4. 多路转接IO

多路转接可以同时处理多个文件描述符,并且它只负责IO过程中的一个过程:等待事件就绪(数据拷贝它不关心,也不处理)。

  • 多路转接虽然也是阻塞等待,但是它与前面不同的是,它可以同时阻塞等待多个文件描述符,将多个文件描述符的等待时间重叠在一起,这些文件描述符可以在任意时刻就绪,只要其中一个文件描述符的事件就绪了,上层就可以处理这个文件描述符,此时上层绝不会被阻塞,因为此时这个事件已经就绪;
  • 通过多路转接,执行流可以将对多个文件描述符的IO操作集中在一起等待,当其中任何一个文件描述符上的IO事件就绪时,就会通知应用程序,从而避免了阻塞并提高了IO效率。

3.3.5. 异步IO

  • 可以看到,在整个IO过程中,这个应用程序没有参与其中,表现为,既没有等待数据就绪,也没有拷贝数据,因此,该执行流完全脱离IO过程,故它是异步IO;
  • 在整个IO过程中,等待数据就绪是内核完成的,将数据在内核和应用层拷贝也是操作系统进行的,数据拷贝完成后,通知应用程序;
  • 在整个IO过程中,应用程序可以在此期间处理其他任务。

4. 非阻塞IO

一个文件描述符或者套接字,默认情况下,都是阻塞式IO,而接下来,我们需要自己通过 fcntl 系统调用将特定文件描述符设定为非阻塞;

因此,我们先来见见 fcntl 系统调用吧 😊~~~~。

当然,也有其他的方法可以设置为非阻塞,比如,open 打开一个文件时,有一个选项,O_NONBLOCK 或者 O_NDELAY。

还比如, 创建一个套接字时,我们也可以设置选项,SOCK_NONBLOCK,设置为非阻塞;

但在后续处理过程中,对于文件描述符或者套接字,我们都会使用 fcntl 系统调用接口,以一种统一的方式来设置非阻塞; 

事实上,一个文件在读写数据时,阻塞和非阻塞无外乎就是文件的一个属性罢了;

4.1. fcntl 系统调用

fcntl() 是一个Linux系统调用,用于对文件描述符 (flie descriptor) 进行控制操作。

它的第二个参数 cmd 是一个整数,指定了要执行的操作类型,其余的参数取决于具体的操作类型。
函数原型如下:

man 2 fcntl --- 在2号手册
NAME
    fcntl --- manipulate file descriptor
SYNOPSIS
    #include <unistd.h>
    #include <fcntl.h>
    int fcntl(int fd, int cmd, ... /* arg */ );

RETURN VALUE
    on error, -1 is returned, and errno is set appropriately.
    For a successful call, the return value depends on the operation.

fcntl 根据传入的值不同, 其可变参数也不相同,在这列举几个,详细请看手册,或者文档:

  • F_SETFL:获取文件状态标志;
  • F_GETFL:设置文件状态标志;
  • F_DUPFD:复制文件描述符;
  • F_SETFD:设置文件描述符标志;
  • F_GETFD:获取文件描述符标志;
  • F_SETLK:设置文件锁;
  • F_GETLK:获取文件锁;

通过 fcntl() 系统调用将特定文件描述符设置为非阻塞的大致思路:

  1. 通过 cmd = F_GETFL 在底层获取当前文件描述符的文件状态标志,这个文件状态标志可以理解为一个位图;
  2. 通过 cmd = f_SETFL 设置当前文件描述符的文件状态标志,因为目的是非阻塞,故:文件状态标志 按位或 O_NONBLOCK,这里的文件状态标志就是步骤1获得的文件状态标志。

如下:

bool SetNonBlock(int fd)
{
  // 步骤一
  // 通过F_GETFL获取当前fd对应的文件状态标志
  // 可以将该文件状态标志(fl)理解为一个位图
  int fl = fcntl(fd, F_GETFL);
  if(fl == -1)
  {
    std::cout << "fcntl error" << std::endl;
    return false;
  }

  // 步骤二
  // 获取文件描述符的文件状态标志成功后
  // 将该文件描述符设置为非阻塞
  fcntl(fd, F_SETFL, fl | O_NONBLOCK);
  return true;
}

注意:

对于一个文件描述符而言,只需要通过 fcntl() 设置一次即可;

如果文件描述符设置为非阻塞,此时 read 时,我们需要通过返回值判定不同的处理方式,比如:

  • 如果返回值 > 0,代表读取成功;
  • 如果返回值 == -1,此时我们需要再次判断,是读取错误,还是底层数据没有就绪呢?

因此,我们需要通过 errno 这个全局变量,判别是读取错误,还是底层数据没有就绪, 比如:

  • 如果 errno == 11,即 errno == EWOULDBLOCK,那么代表着底层数据没有就绪,try again 即可;
  • 如果 errno == 4,即 errno == EINTR,代表着此次IO可能被某个信号中断,try again 即可;
  • 如果是其他错误码,进行差错处理。

简单实现一个,非阻塞轮询式IO,实现如下:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>

bool SetNonBlock(int fd)
{
  // 步骤一
  // 通过F_GETFL获取当前fd对应的文件状态标志
  // 可以将该文件状态标志(fl)理解为一个位图
  int fl = fcntl(fd, F_GETFL);
  if(fl == -1)
  {
    std::cout << "fcntl error" << std::endl;
    return false;
  }

  // 步骤二
  // 获取文件描述符的文件状态标志成功后
  // 将该文件描述符设置为非阻塞
  fcntl(fd, F_SETFL, fl | O_NONBLOCK);
  return true;
}

int main()
{
  // 众所周知, 从0号文件描述符读取内容默认是以阻塞方式进行的
  // 但我们可以通过 fcntl 系统调用设置非阻塞IO
  if(!SetNonBlock(0)) exit(1);
  // 只需要设置一次即可
  // 后续的0号文件描述符就是非阻塞的
  
  char buffer[1024] = {0};
  while(true)
  {
    sleep(1);
    errno = 0;
    ssize_t real_size = read(0, buffer, sizeof buffer - 1);
    if(real_size > 0)
    {
      buffer[real_size] = 0;
      std::cout << "echo: " << buffer << "errno: " << errno << " errnoMessage: " << strerror(errno) << std::endl;
    }
    else
    {
      if(errno == EAGAIN || errno == EWOULDBLOCK)
      {
        // #define EWOULDBLOCK EAGAIN
        // #define EAGAIN 11
        // 当errno == 11时, 其实并没有错, 只不过底层数据没就绪, 再试一次吧~~~~
        std::cout << "Resource temporarily unavailable, Try again" << std::endl;
        continue;
      }
      else if(errno == EINTR)
      {
        // 此时也并不代表有错, 此次IO可能被某个信号中断了, Try again
        std::cout << "IO operation was interrupted by a signal, Try again" << std::endl;
        continue;
      }
      else
      {
        // 其他错误, 差错处理即可
        std::cout << "other error" << std::endl;
        exit(2);
      }
    }
  }
  return 0;
}

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

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

相关文章

怎么清除3D模型杂质?---模大狮模型网

在进行3D建模过程中&#xff0c;模型可能会受到各种杂质的影响&#xff0c;这些杂质可能来自于模型本身的结构问题、导入导出过程中的错误、或者是不当的编辑操作所留下的痕迹。清除这些杂质是保证模型质量和渲染效果的关键步骤之一。本文将介绍几种常见的清除3D模型杂质的方法…

【Android】重温Activity生命周期

前言 Android中用得最多的组件是Activity&#xff0c;而它的生命周期也是最基础的知识&#xff0c;从刚接触Android到工作中会频繁依赖这部分知识。可能大多数人能说出页面新建到页面关闭会走的生命周期&#xff1a;onCreate、onStart、onResume、onPause、onStop、onDestory&…

计算机网络 Cisco路由器基本配置

一、实验内容 1、按照下表配置好PC机IP地址和路由器端口IP地址 2、配置好路由器特权密文密码“abcd&#xff0b;两位班内序号”和远程登录密码“star” 3、验证测试 a.验证各个接口的IP地址是否正确配置和开启 b.PC1 和 PC2 互ping c.验证PC1通过远程登陆到路由器上&#…

VMware安装Linux虚拟机(rocky9)

软件准备&#xff1a; VMware虚拟机ISO系统镜像文件 选择创建虚拟机→典型→下一步→点击稍后安装操作系统 选择Linux系统和对应版本 输入虚拟机名称和选择保存位置 设置磁盘大小 根据需要自定义硬件配置→完成 然后点击编辑虚拟机设置→CD/DVD→选择ISO镜像 然后开启虚拟机→…

Mysql的函数和约束

函数和约束 文章目录 函数和约束函数字符串函数数值函数日期函数流程函数 约束概念目的分类使用案例外键约束 函数 使用 select 函数();字符串函数 数值函数 日期函数 流程函数 约束 概念 约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的 保证…

Windows VS2019 JsonCpp库下载编译

下载地址 jsoncpp下载地址 编译 打开cmake-gui 设置代码地址&#xff0c;生成地址->点击configure->设置VS2019-x64 如下&#xff1a; 再点击generate 进入到build的目录打开jsoncpp.sln文件&#xff0c;进行编译即可 生成目录&#xff1a; build\lib\Release 头…

零基础自学Python,啃透这五本书就够了!

选择合适的学习资源 在自学Python的前期&#xff0c;选择一本适合初学者的Python入门书籍或在线教程&#xff0c;从基础开始学习&#xff0c;好的入门书籍或在线教程会按照逻辑顺序组织知识&#xff0c;从基础概念开始&#xff0c;逐步引导你深入学习Python编程语言。这种系统…

SQL SERVER的安装

目录 1.百度SQL SERVER找到图下的所显示的&#xff0c;点击进去 2.找到图下红色框起来的&#xff0c;点击立即下载​ 3.下载好之后点开&#xff0c;选择下载介质 4.SQLSERVER下载成功之后选择打开文件夹​ 6.双击后缀名是.iso的镜像文件 7.双击setup.exe进行安装​ 8.安…

LlamaIndex 组件 - Loading

文章目录 一、概览加载Transformations将所有内容放在一起抽象 二、文档/节点概览1、概念2、使用模式文件节点 三、定义和定制文档1、定义文档2、自定义文档2.1 元数据2.2 自定义id2.3 高级 - 元数据定制1&#xff09;自定义LLM元数据文本2&#xff09;自定义嵌入元数据文本3&a…

RD77MS2 三菱iQ-R系列2轴简单运动模块(SSCNETⅢ/H型)

RD77MS2 三菱iQ-R系列2轴简单运动模块(SSCNETⅢ/H型) RD77MS2用户手册,RD77MS2外部连接,RD77MS2规格。RD77MS2参数说明&#xff1a;2轴;SSCNETⅢ/H连接&#xff0c;位置控制、同步控制、速度.转矩控制、轨迹控制;控制单位mm、inch、degree、pulse;定位数据600数据轴。 RD77MS2图…

浅尝 express + ORM框架 prisma 的结合

一、prisma起步 安装&#xff1a; npm i prisma -g查看初始化帮助信息&#xff1a; prisma init -h查看初始化帮助信息结果&#xff1a; Set up a new Prisma projectUsage$ prisma init [options] Options-h, --help Display this help message --datasource-provider …

Databend 开源周报第 140 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 支持 EXECUTE I…

thymeleaf模板引擎的使用

thymeleaf模板引擎的使用 ​ 在早期开发的时候&#xff0c;我们完成的都是静态页面也就是html页面&#xff0c;随着时间轴的发展&#xff0c;慢慢的引入了jsp页面&#xff0c;当在后端服务查询到数据之后可以转发到jsp页面&#xff0c;可以轻松的使用jsp页面来实现数据的显示及…

JAVA_类和对象(1)

认识面向对象 Java是一门纯面向对象的语言(Object Oriented Program, OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。  面向过程和面相对象并不是一门语言&#xff0c;而是解决…

jetson系列开发板使用虚拟机烧录系统时,遇见无法识别开发板的情况

在双系统中的ubuntu系统烧录没问题&#xff0c;但是电脑Ubuntu系统由于版本低&#xff0c;所以没有网络&#xff0c;烧录起来还的连网线&#xff0c;所以问了开发板的工程师&#xff0c;所幸&#xff0c;解决了问题&#xff0c;很感谢工程师的指导&#xff0c;特此记录一下&…

前端跨域怎么办?

如果网上搜到的方法都不可行或者比较麻烦&#xff0c;可以尝试改变浏览器的设置&#xff08;仅为临时方案&#xff09; 1.新建一个Chrome浏览器的快捷方式 2.鼠标右键&#xff0c;进入属性&#xff0c;将以下命令复制粘贴到目标位置&#xff08;可根据Chrome实际存放位置修改…

Innodb之redo日志

Innodb引擎执行流程 redo log ​ MySQL中的redo log&#xff08;重做日志&#xff09;是实现WAL&#xff08;预写式日志&#xff09;技术的关键组件&#xff0c;用于确保事务的持久性和数据库的crash-safe能力。借用《孔乙己》中酒店掌柜使用粉板记录赊账的故事&#xff0c;…

最新AI创作系统ChatGPT网站源码AI绘画,GPTs,AI换脸支持,GPT联网提问、DALL-E3文生图

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

Oracle Discoverer Plus:下载公司的未交货销售订单

新建一个Report。 1&#xff0c;打开公司的Order management数据库 2&#xff0c;把需要的一些Field移到Report中&#xff0c;比如订单号&#xff0c;订单数量&#xff0c;fillfuled数量&#xff0c;或者Shipped数量&#xff0c;等等 3&#xff0c;其实这个笔记主要是为了记录…