Linux --- 高级IO

目录

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号手册
NAMEfcntl --- manipulate file descriptor
SYNOPSIS#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );RETURN VALUEon 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 系统调用设置非阻塞IOif(!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 againstd::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.mzph.cn/news/820547.shtml

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

相关文章

抖店如何打造一款爆品?按照这三个阶段做,爆单很简单

大家好&#xff0c;我是电商笨笨熊 做抖音小店不懂得怎么选品&#xff0c;那还怎么出销量&#xff1f; 选品时很多新手最苦恼的问题&#xff0c;不知道从哪里选品更容易选中爆品、不懂得什么样的品才是爆品&#xff0c;更不懂得如何打造一款爆品。 那么今天&#xff0c;我们…

创建影子用户

文章目录 1.认识影子用户2.创建隐藏账户并加入管理员组3.修改注册表3.删除用户4.添加管理员权限 1.认识影子用户 影子用户通常指的是那些在系统用户列表中不可见&#xff0c;但在某些情况下可以进行操作的用户。在内网渗透过程中&#xff0c;当我们拿到shell时&#xff0c;肯定…

android11 如何修改状态栏的背景

修改status_bar.xml &#xff1a; <LinearLayout android:id"id/status_bar_contents"android:background"#1ABC9C"android:layout_width"match_parent"android:layout_height"match_parent"android:paddingStart"dimen/statu…

【重回王座】ChatGPT发布最新模型gpt-4-turbo-2024-04-09

今天&#xff0c;新版GPT-4 Turbo再次在大型模型排行榜上荣登榜首&#xff0c;成功超越了此前领先的Claude 3 Opus。另外&#xff0c;新模型在处理长达64k的上下文时&#xff0c;性能竟能够与旧版在处理26k上下文时的表现相当。 目前GPT-4 Turbo仅限于ChatGPT Plus的用户&…

Nginx服务 重写功能与反向代理

六、重写功能 rewrite Nginx服务器利用 ngx_http_rewrite_module 模块解析和处理rewrite请求&#xff0c;此功能依靠 PCRE(perl compatible regular expression)&#xff0c;因此编译之前要安装PCRE库&#xff0c;rewrite是nginx服务器的重要功能之一&#xff0c;用于实现URL的…

DBA面试总结(Mysql篇)

一、delete与trancate的区别 相同点 1.两者都是删除表中的数据&#xff0c;不删除表结构 不同点 1.delete支持按条件删除&#xff0c;TRUNCATE不支持。 2.delete 删除后自增列不会重置&#xff0c;而TRUNCATE会被重置。 3.delete是逐条删除&#xff08;速度较慢&#xff09…

【linux编译报错】g++: error:elf_x86_64:没有那个文件或目录

背景 gcc版本已经是高版本了&#xff0c;9开头了&#xff0c;但是在IDE编译的时候报错&#xff1a; 但是记得自己没有配置过这种参数&#xff0c;只能一步步查了 解决方法 步骤1&#xff1a;先google看了下别人是否碰到该问题 找到一个解决方法说&#xff1a; 在Makefile中…

配置路由器实现互通

1.实验环境 实验用具包括两台路由器(或交换机)&#xff0c;一根双绞线缆&#xff0c;一台PC&#xff0c;一条Console 线缆。 2.需求描述 如图6.14 所示&#xff0c;将两台路由器的F0/0 接口相连&#xff0c;通过一台PC 连接设备的 Console 端口并配置P地址&#xff08;192.1…

如何在CentOS安装Firefox并结合内网穿透工具实现公网访问本地火狐浏览器

文章目录 1. 部署Firefox2. 本地访问Firefox3. Linux安装Cpolar4. 配置Firefox公网地址5. 远程访问Firefox6. 固定Firefox公网地址7. 固定地址访问Firefox Firefox是一款免费开源的网页浏览器&#xff0c;由Mozilla基金会开发和维护。它是第一个成功挑战微软Internet Explorer浏…

win11电脑驱动怎么更新,windows11更新驱动

驱动是指计算机里软件的程序,硬件的运作离不开驱动的支持,因为驱动就是使得硬件和电脑系统沟通的桥梁。既然驱动如此重要,那么不装肯定不行,如果有问题,也要及时地修复和更新。最近,有位win11用户,想要了解win11电脑驱动怎么更新?接下来,教程会带来两种更新win11驱动的…

LlamaIndex 组件 - Loading

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

【数据结构与算法】最大公约数与最小公倍数

最大公因数&#xff08;英语&#xff1a;highest common factor&#xff0c;hcf&#xff09;也称最大公约数&#xff08;英语&#xff1a;greatest common divisor&#xff0c;gcd&#xff09;是数学词汇&#xff0c;指能够整除多个非零整数的最大正整数。例如8和12的最大公因数…

【Java探索之旅】数组使用 初探JVM内存布局

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、数组的使用1.1 元素访问1.2 数组遍历 二、JVM的内存布局&#x1f324;️全篇总结 …

WP免费主题下载

免费wordpress模板下载 高端大气上档次的免费wordpress主题&#xff0c;首页大图全屏显示经典风格的wordpress主题。 https://www.wpniu.com/themes/289.html 免费WP主题 蓝色简洁实用的wordpress免费主题模板&#xff0c;免费主题资源分享给大家。 https://www.wpniu.com/…

基于Springboot+Vue的Java项目-校园管理系统(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

在Vue项目使用kindEditor富文本编译器

第一步 npm install kindeditor第二步&#xff0c;建立kindeditor.vue组件 <template><div class"kindeditor"><textarea :id"id" name"content" v-model"outContent"></textarea></div> </templa…

浅尝 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 …

JAVA_类和对象(1)

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

8thWall vs. AR.js

对于熟悉 JavaScript、WebGL 和 HTML5 等 Web 技术的数字创作者来说&#xff0c;8th Wall 提供了功能丰富且强大的 AR 开发平台&#xff0c;尽管价格较高。 然而&#xff0c;新手开发人员和专注于基于标记的 AR 的开发人员可能会发现 AR.js 更易于使用且更经济实惠。 1、8th Wa…

拉普拉斯金字塔的频谱分析

1. 基本分析 拉普拉斯金字塔分解&#xff0c;主要由以下步骤组成&#xff1a; 对输入图像 L0 进行低通滤波&#xff0c;其中常采用高斯滤波&#xff1b;对低通滤波后的图像进行 1/2 倍率的下采样&#xff0c;这里的下采样通常是指直接取偶行且偶列&#xff08;以 0 开始计&am…