当前位置: 首页 > news >正文

前后端中的异步和事件机制 | 前后端开发

d1114f2c42cf35573b1e83efbf648ce4.png

前言

在前后端程序设计开发工作中,小伙伴们一定都接触过事件、异步这些概念。出现这些概念的原因之一是,我们的代码在执行过程中所涉及的逻辑在不同的场合下执行时间的期望是各不相同的。为了尽量做到充分利用CPU等资源做尽可能多的事,免不了通过异步和事件机制的配合来实现系统资源分时复用的效率最大化。相信这个时候后端开发同学肯定会说,我们多线程、协程等并发编程的概念和机制都流行很久了,但大家有没有思考过,服务端各种语言比如golang, JAVA等已经在语言层面帮大家做了相当多的系统底层封装工作。抽象到系统层面,相信大家都知道大名鼎鼎的epoll机制,其核心目标还是实现系统资源分时复用的效率最大化。下面就让我们一起来看看,前后端应用开发场景中异步和事件机制有什么异同吧。

前端中的异步和事件机制

相信前端同学对异步和事件机制会更加敏感,这主要是因为JavaScript的特性导致异步和事件成了语言学习中的必会核心知识点之一。

3d70b690464953581c391fe959c3e76f.png

作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。

另外,因为 JavaScript 是单线程的,在某一时刻内只能执行特定的一个任务,并且会阻塞其它任务执行。那么对于类似 I/O 等耗时的任务,就没必要等待他们执行完后才继续后面的操作。在这些任务完成前,JavaScript 完全可以往下执行其他操作,当这些耗时的任务完成后则以回调的方式执行相应处理。这些就是 JavaScript 与生俱来的特性:异步与回调。

当然对于不可避免的耗时操作(如:繁重的运算,多重循环),HTML5 提出了Web Worker,它会在当前 JavaScript 的执行主线程中利用 Worker 类新开辟一个额外的线程来加载和运行特定的 JavaScript 文件,这个新的线程和 JavaScript 的主线程之间并不会互相影响和阻塞执行,而且在 Web Worker 中提供了这个新线程和 JavaScript 主线程之间数据交换的接口:postMessage 和 onMessage 事件。但在 HTML5 Web Worker 中是不能操作 DOM 的,任何需要操作 DOM 的任务都需要委托给 JavaScript 主线程来执行,所以虽然引入 HTML5 Web Worker,但仍然没有改线 JavaScript 单线程的本质。

2250d3712c173311952cea8de573ae78.png

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。js引擎执行异步代码而不用等待,是因有为有 消息队列和事件循环。

消息队列:消息队列是一个先进先出的队列,它里面存放着各种消息。

事件循环:事件循环是指主线程重复从消息队列中取消息、执行的过程。

实际上,主线程只会做一件事情,就是从消息队列里面取消息、执行消息,再取消息、再执行。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。整个机制如下图所示:

5170404eb9b399c4b7a27b6fe41d7896.png

这里有几个概念:事件循环、调用栈(执行站)、微任务、宏任务、事件队列机制如下图所示:

38167cd178b7e79187b0693853bbc6e9.png

其中上图中的web api和对应的queue在实际应用场景对应两个:微任务和微任务队列、宏任务和宏任务队列,微任务和宏任务的定义包含:

6271de40f5445eb32b0fed6f7be4550c.png

f7772f1263b65399e80098e762ade0fd.png

不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同(宏任务)的Event Queue。而Promise和process.nextTick会进入相同(微任务)的Event Queue。

1.「宏任务」、「微任务」都是队列,一段代码执行时,会先执行宏任务中的同步代码。

2.进行第一轮事件循环的时候会把全部的js脚本当成一个宏任务来运行。

3.如果执行中遇到setTimeout之类宏任务,那么就把这个setTimeout内部的函数推入「宏任务的队列」中,下一轮宏任务执行时调用。

4.如果执行中遇到 promise.then() 之类的微任务,就会推入到「当前宏任务的微任务队列」中,在本轮宏任务的同步代码都执行完成后,依次执行所有的微任务。

5.第一轮事件循环中当执行完全部的同步脚本以及微任务队列中的事件,这一轮事件循环就结束了,开始第二轮事件循环。

6.第二轮事件循环同理先执行同步脚本,遇到其他宏任务代码块继续追加到「宏任务的队列」中,遇到微任务,就会推入到「当前宏任务的微任务队列」中,在本轮宏任务的同步代码执行都完成后,依次执行当前所有的微任务。

7.开始第三轮,循环往复...

下面举例子来说明

例1

0a44de1dd24d857db86fd6ae77d5ada1.png

1、new Promise 的函数体是同步脚本所以先执行的是1、2。

2.3和4都是微任务,这里因为有await,4要等Promise.then()之后才会执行。console.log('4')已经被放在await语法糖生成的Promise.then里了,而await的等待必须要等后面Promise.then之后才会结束。

例2

824bc1f6460390261b3ad969b8f498b6.png

1.6是宏任务在下一轮事件循环执行

2.先同步输出1,然后调用了async1(),输出2。

3.await async2() 会先运行async2(),5进入等待状态。

4.输出3,这个时候先执行async函数外的同步代码输出4。

5.最后await拿到等待的结果继续往下执行输出5。

6.进入第二轮事件循环输出6。

例3

2f4ad94bf947c083961570e0101b54dd.png

1.首先输出1,然后进入async1()函数,输出2。

2.await后面虽然是一个直接量,但是还是会先执行async函数外的同步代码。

3.输出3,进入Promise输出4,then回调进入微任务队列。

4.现在同步代码执行完了,回到async函数继续执行输出5。

5.最后运行微任务输出6。

例4

dcc95daba279045d5b7721cbe6884376.png

1.首先输出同步代码1,然后进入async1方法输出2。

2.因为遇到await所以先进入async2方法,后面的7被放入微任务队列。

3.在async2中输出3,现在跳出async函数先执行外面的同步代码。

4.输出4,5。then回调6进入微任务队列。

5.现在宏任务执行完了,微任务先入先执行输出7、6。

6.第二轮宏任务输出8。

例5

ea461fe760dd334e50b27abe0f5e75f0.png

1.先输出1,2,3。3后面的then进入微任务队列。

2.执行外面的同步代码,输出4,5。4后面的then进入微任务队列。

3.接下来执行微任务,因为3后面的then先进入,所以按序输出6,7。

4.下面回到async1函数,await关键字等到了结果继续往下执行。

5.输出8,进行下一轮事件循环也就是宏任务二,输出9。

例6

225ab537d88d88ad85e29118d8c26880.png

0b90c58e2d845d9b2a4486bb084925fb.png

1.函数async1和async2只是定义先不去管他,首先输出1。

2.setTimeout作为宏任务进入宏任务队列等待下一轮事件循环。

3.进入async1()函数输出2,await下面的代码进入等待状态。

4.进入async2()输出3,then回调进入微任务队列。

5.现在执行外面的同步代码,输出4,5,then回调进入微任务队列。

6.按序执行微任务,输出6,7。现在回到async1函数。

7.输出data,也就是await关键字等到的内容,接着输出8。

8.进行下一轮时间循环输出9。

执行结果:1 - 2 - 3 - 4 - 5 - 6 - 7 - await的结果 - 8 - 9

例7

4a4cccda59822e6ad24eafa44cabb32e.png

e086b60d00545b7641c39f7247a232c2.png

1.setTimeout作为宏任务进入宏任务队列等待下一轮事件循环。

2.先执行async1函数,输出1,6进入等待状态,现在执行async2。

3.输出2,then回调进入微任务队列。

4.接下来执行外面的同步代码输出3,then回调进入微任务队列。

5.按序执行微任务,输出4,5。下面回到async1函数。

6.输出了4之后执行了return data,await拿到了内容。

7.继续执行输出6,执行了后面的 return data 才触发了async1()的then回调输出7以及data。

8.进行第二轮事件循环输出8。

执行结果:1 - 2 - 3 -4 - 5 - 6 - 7 - async2的结果 - 8

后端中的异步和事件机制

cc7a2bf1bfdcbdb3827309a258c00162.png

后端的情况会根据语言的不同有细微差异,但核心原理和机制是一致的,这里我们以golang为例进行分析。

先说异步

在golang中,异步调用的实现和其他编程语言有所不同,golang采用goroutine(协程)的方式实现异步调用。goroutine是一种轻量级的线程,可以在程序中创建多个协程,每个协程都是独立的,并且可以并发执行。

39bf299a894cada34c7702ace5504c16.png

在实际应用中,异步调用常用于以下几个场景:

1.网络请求

在网络通信中,由于网络状况的不确定性,请求的响应时间可能会非常长,如果采用同步调用的方式,就会造成程序长时间阻塞,影响用户体验。因此,我们可以采用异步调用的方式,在请求之后不必等待响应,而是继续执行其他任务,等到响应到来之后再处理。

2.文件操作

对于一些文件操作,可能需要进行大量的I/O操作,如读取文件内容、写入文件等。这些I/O操作比较耗时,如果采用同步调用的方式,可能会造成程序阻塞并且效率低下。因此,我们可以采用异步调用的方式,在文件操作需要花费大量时间时,使用goroutine执行任务,不会影响主线程的正常运行。

3.定时任务

在一些定时任务中,可能需要执行一些比较耗时的操作。如果采用同步调用的方式,可能会影响程序的时间精度和稳定性。因此,我们可以使用异步调用的方式,在主线程执行定时任务的同时,开启goroutine执行具体的操作任务,不会影响程序的精度和稳定性。

在golang中,我们可以使用goroutine和channel来实现异步调用的功能。

1.使用goroutine实现异步调用

在golang中,开启一个goroutine非常简单,只需要在函数前面加上go关键字即可,例如:

752ab447ec71136bd0e84578f4d99d0f.png

上述代码就是在新的goroutine中执行一个任务。我们来看一个完整的示例代码:

34fe1c3c5ad99631ee1a5764e0ce4899.png 

通过上述代码,我们可以看到,程序开启了一个goroutine执行任务,同时主线程也在执行另一个任务。在程序运行过程中,主线程和goroutine可以同时运行,相互不影响。

2.使用channel实现异步调用

在golang中,channel是goroutine之间通信的一种方式,我们可以使用channel来实现异步调用。我们可以创建一个带有缓冲区的channel,然后在goroutine中执行任务,并将结果通过channel传递给主线程,如下所示:

9baa9cf377f2a745e31cb965da2a04c3.png

在上述代码中,我们创建了一个带有缓冲区的channel,并在goroutine中执行一个任务,任务的结果通过channel传递给主线程。主线程通过循环读取channel中的数据,当channel关闭时,通过ok变量来判断循环是否结束,从而确保程序能够正常退出。

说完异步,让我们回到事件机制

在服务端中涉及到事件应用主要是发生I/O请求时,而这其中网络I/O在golange中占很重要的比重。当设备上有数据到达的时候,会给 CPU 的相关引脚上触发⼀个电压变化,以通知 CPU 来处理数据。也可以把这个叫 硬中断。

但是我们知道,cpu运行速度很快,但是网络读取数据会很慢,这时候就会长期占用cpu,导致cpu无法处理其他事件,比如,鼠标移动。那么在linux中是怎么解决掉这个问题的呢?linux内核将中断处理拆分开,拆分为了2个部分,一个是上面提到的 硬中断,另外就是 软中断。

第一部分接收到cpu电压变化,产生硬中断,然后只做最简单的处理,然后异步的交给硬件去接收信息到缓冲区。这个时候,cpu就已经可以接收其他中断信息过来了。

第二部分就是软中断部分,软中断是怎么做的呢?其实就是对内存的二进制位进行变更,类似于我们平常写业务常用的到的status字段一样,比如网络Io中,当缓冲区接收数据完毕,会将当前状态改为完成。举个例子,epoll读取某个io时间读取完数据时,并不会直接进入就绪态,而是等下次循环遍历判断状态,才会将这个fd塞入就绪列表(当然,这个时间很短,不过相对于cpu来说,这个时间就很长了)。

2.4 以后的内核版本采⽤的下半部实现⽅式是软中断,由 ksoftirqd 内核线程全权处理。和硬中断不同的是,硬中断是通过给 CPU 物理引脚施加电压变化,⽽软中断是通过给内存中的⼀个变量的⼆进制值以通知软中断处理程序。

这也就是为什么知道2.6才有epoll(正式引入)使用的原因,2.4以前内核都不支持这种方式。

总体的数据流转图如下:

e32f1ca31e33fab97fc3ee6a34d835b4.png

一个数据从到达网卡,要经历以下步骤才会完成一次数据接收:

  • 数据包从外面的网络进入物理网卡。如果目的地址不是该网卡,且该网卡没有开启混杂模式,该包会被网卡丢弃。

  • 网卡将数据包通过DMA的方式写入到指定的内存地址,该地址由网卡驱动分配并初始化。注:老的网卡可能不支持DMA,不过新的网卡一般都支持。

  • 网卡通过硬件中断(IRQ)通知CPU,告诉它有数据来了

  • CPU根据中断表,调用已经注册的中断函数,这个中断函数会调到驱动程序(NIC Driver)中相应的函数

  • 驱动先禁用网卡的中断,表示驱动程序已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知CPU了,这样可以提高效率,避免CPU不停的被中断。

  • 启动软中断。这步结束后,硬件中断处理函数就结束返回了。由于硬中断处理程序执行的过程中不能被中断,所以如果它执行时间过长,会导致CPU没法响应其它硬件的中断,于是内核引入软中断,这样可以将硬中断处理函数中耗时的部分移到软中断处理函数里面来慢慢处理。

  • 内核中的ksoftirqd进程专门负责软中断的处理,当它收到软中断后,就会调用相应软中断所对应的处理函数,对于上面第6步中是网卡驱动模块抛出的软中断,ksoftirqd会调用网络模块的net_rx_action函数

  • net_rx_action调用网卡驱动里的poll函数来一个一个的处理数据包

  • 在pool函数中,驱动会一个接一个的读取网卡写到内存中的数据包,内存中数据包的格式只有驱动知道

  • 驱动程序将内存中的数据包转换成内核网络模块能识别的skb格式,然后调用napi_gro_receive函数

  • napi_gro_receive会处理GRO相关的内容,也就是将可以合并的数据包进行合并,这样就只需要调用一次协议栈。然后判断是否开启了RPS,如果开启了,将会调用enqueue_to_backlog

  • 在enqueue_to_backlog函数中,会将数据包放入CPU的softnet_data结构体的input_pkt_queue中,然后返回,如果input_pkt_queue满了的话,该数据包将会被丢弃,queue的大小可以通过net.core.netdev_max_backlog来配置

  • CPU会接着在自己的软中断上下文中处理自己input_pkt_queue里的网络数据(调用__netif_receive_skb_core)

  • 如果没开启RPS,napi_gro_receive会直接调用__netif_receive_skb_core

  • 看是不是有AF_PACKET类型的socket(也就是我们常说的原始套接字),如果有的话,拷贝一份数据给它。tcpdump抓包就是抓的这里的包。

  • 调用协议栈相应的函数,将数据包交给协议栈处理。

  • 待内存中的所有数据包被处理完成后(即poll函数执行完成),启用网卡的硬中断,这样下次网卡再收到数据的时候就会通知CPU

epoll

b077919a66960166cb157e675c0072c6.png

poll函数
这里的poll函数是说注册的回调函数,在软中断中进行处理的。比如epoll程序,会注册一个“ep_poll_callback”

以go epoll为例:

go: accept –> pollDesc.Init -> poll_runtime_pollOpen –> runtime.netpollopen(epoll_create) -> epollctl(EPOLL_CTL_ADD)

go: netpollblock(gopark),让出cpu->调度回来,netpoll(0)将协程写入就绪态->其他操作…

epoll thread: epoll_create(ep_ptable_queue_proc,注册软中断到ksoftirqd,将方法ep_poll_callback注册到)->epoll_add->epoll_wait(ep_poll让出cpu)

core: 网卡接收到数据->dma+硬中断->软中断->系统调度到ksoftirqd,处理ep_poll_callback(这里要注意,新的连接进入到程序,不是通过callback,而是走accept)->获取到之前注册的fd句柄->copy网卡数据到句柄->根据事件类型,对fd进行操作(就绪列表)

7c23473a9b1f22961adc52a1d10586ba.png

部分代码

go: accept

bde05096a13f7d57c857727d921005fe.png

6803f75293a83b03931b09e424d319b4.png

epoll源码

6e431fc072042f4357ba8d34d82598fd.png

基础数据结构

epoll用kmem_cache_create(slab分配器)分配内存用来存放struct epitem和struct eppoll_entry。当向系统中添加一个fd时,就创建一个epitem结构体,这是内核管理epoll的基本数据结构:

c3e3da30ae6c8f6caa27019c7ba60d62.png

而每个epoll fd(epfd)对应的主要数据结构为:

120abdd6001452bb4db0c448b240babc.png

struct eventpoll在epoll_create时创建。

41615f6c9f4494bc5356262175b8099f.png

其中,ep_alloc(struct eventpoll **pep)为pep分配内存,并初始化。
其中,上面注册的操作eventpoll_fops定义如下:

d9773a58989526387c3e282daae2b75a.png

这样说来,内核中维护了一棵红黑树,大致的结构如下:
clip_image002
接着是epoll_ctl函数(省略了出错检查等代码):

00f0430a4466fead1bad503cdb0ac13f.png

ep_insert的实现如下:
 

41ed52727d478dfbfb2e425c7fd12564.png

这两个函数将ep_ptable_queue_proc注册到epq.pt中的qproc。

执行f_op->poll(tfile, &epq.pt)时,XXX_poll(tfile, &epq.pt)函数会执行poll_wait(),poll_wait()会调用epq.pt.qproc函数,即为ep_ptable_queue_proc。

ep_ptable_queue_proc函数如下:

f8be5227ca5e8b4a8621f984007b2b2f.png

ep_ptable_queue_proc(ep_poll_callback)其中struct eppoll_entry定义如下:

0546340c3e7aafcc2502e6b6a26c337a.png

在ep_ptable_queue_proc函数中,引入了另外一个非常重要的数据结构eppoll_entry。

eppoll_entry要完成epitem和epitem事件发生时的callback(ep_poll_callback)函数之间的关联。首先将eppoll_entry的whead指向fd的设备等待队列(同select中的wait_address),然后初始化eppoll_entry的base变量指向epitem,最后通过add_wait_queue将epoll_entry挂载到fd的设备等待队列上。

完成这个动作后,epoll_entry已经被挂载到fd的设备等待队列。

由于ep_ptable_queue_proc函数设置了等待队列的ep_poll_callback回调函数。所以在设备硬件数据到来时,硬件中断处理函数中会唤醒该等待队列上等待的进程时,会调用唤醒函数ep_poll_callback。

95366cb8f6642e23b04261bcce0e8978.png

所以ep_poll_callback函数主要的功能是将被监视文件的等待事件就绪时,将文件对应的epitem实例添加到就绪队列中,当用户调用epoll_wait()时,内核会将就绪队列中的事件报告给用户。

epoll_wait实现如下:
 

6b28ec7321ee10b7223d07fe3bf63dea.png

epoll_wait中对ep_poll进行了调用,ep_poll实现如下:

32f7b7510a5fb2ec0b809906dc847df2.png

94facc49e5161d43a148b4a62729d5d4.png

相关文章:

前后端中的异步和事件机制 | 前后端开发

前言 在前后端程序设计开发工作中,小伙伴们一定都接触过事件、异步这些概念。出现这些概念的原因之一是,我们的代码在执行过程中所涉及的逻辑在不同的场合下执行时间的期望是各不相同的。为了尽量做到充分利用CPU等资源做尽可能多的事,免不了…...

设计模式篇(Java):装饰者模式

👨‍💻本文专栏:设计模式篇-装饰者模式 👨‍💻本文简述:装饰者模式的详解以及jdk中的应用 👨‍💻上一篇文章: 设计模式篇(Java):桥接模式 👨‍&am…...

Spark【RDD编程(三)键值对RDD】

简介 键值对 RDD 就是每个RDD的元素都是 (key,value)类型的键值对,是一种常见的 RDD,可以应用于很多场景。 因为毕竟通过我们之前Hadoop的学习中,我们就可以看到对数据的处理,基本都是以…...

从板凳围观到玩转行家:Moonbeam投票委托如何让普通用户一同参与

今年5月,Moonbeam发起了一项社区链上治理中投票委托反馈的调查。187位社区成员参与了这项调查,调查发现受访者对治理感兴趣,增加参与度只需要进行一些调整,即更简化的投票流程。 治理和去中心化是Web3的核心,随着Moon…...

SpringMVC的文件上传文件下载多文件上传---详细介绍

目录 前言: 一,文件上传 1.1 添加依赖 1.2 配置文件上传解析器 1.3 表单设置 1.4 文件上传的实现 二,文件下载 controller层 前端jsp 三,多文件上传 Controller层 运行 前言: Spring MVC 是一个基于 Java …...

Spark【RDD编程(四)综合案例】

案例1-TOP N个数据的值 输入数据: 1,1768,50,155 2,1218,600,211 3,2239,788,242 4,3101,28,599 5,4899,290,129 6,3110,54,1201 7,4436,259,877 8,2369,7890,27 处理代码: def main(args: Array[String]): Unit {//创建SparkContext对象val conf…...

Golang报错mixture of field:value and value initializers

Golang报错mixture of field:value and value initializers 这个错误跟编程习惯(模式)有关,都知道golang 语言的编程与java /python 以及其他的编程语言相似 ,一通百通,易学万卷书。 编程中同一个结构中要保持唯一模…...

【网络教程】记一次使用Docker手动搭建BT宝塔面板的全过程(包含问题解决如:宝塔面板无法开启防火墙,ssh,nginx等)

文章目录 准备安装安装宝塔面板开启ssh和修改ssh的密码导出镜像问题解决宝塔面板无法开启防火墙无法启动ssh设置密码nginx安装失败设置开机启动相关服务准备 演示的系统环境:Ubuntu 22.04.3 LTS更新安装/升级docker到最新版本升级docker相关命令如下# 更新软件包列表并自动升级…...

【大虾送书第九期】速学Linux:系统应用从入门到精通

目录 🍭写在前面 🍭为什么学习Linux系统 🍭Linux系统的应用领域 🍬1.Linux在服务器的应用 🍬2.嵌入式Linux的应用 🍬3.桌面Linux的应用 🍭Linux的版本选择 &a…...

docker相关命令

####### 帮助启动类命令 ########## 启动docker systemctl start docker 停止docker systemctl stop docker 重启docker systemctl restart docker 查看docker状态 systemctl status docker 开机启动 systemctl enable docker 查看docker概要信息 docker info 查看…...

【Redis】4、rsync远程同步

与inodify结合使用,实现实时同步 rsync简介 rsync(Remote Sync,远程同步)是一个开源的快速备份工具,可以在不同主机之间镜像同步整个目录树,;支持增量备份,并保持链接和权限&#…...

无服务架构--Serverless

无服务架构 无服务架构(Serverless Architecture)即无服务器架构,也被称为函数即服务(Function as a Service,FaaS),是一种云计算模型,用于构建和部署应用程序,无需关心…...

2023-09-07 LeetCode每日一题(修车的最少时间)

2023-09-07每日一题 一、题目编号 2594. 修车的最少时间二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数数组 ranks ,表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你…...

数据挖掘实验-主成分分析与类特征化

数据集&代码https://www.aliyundrive.com/s/ibeJivEcqhm 一.主成分分析 1.实验目的 了解主成分分析的目的,内容以及流程。 掌握主成分分析,能够进行编程实现。 2.实验原理 主成分分析的目的 主成分分析就是把原有的多个指标转化成少数几个代表…...

70. 爬楼梯 (进阶),322. 零钱兑换,279.完全平方数

代码随想录训练营第45天|70. 爬楼梯 (进阶,322. 零钱兑换,279.完全平方数 70.爬楼梯文章思路代码 322.零钱兑换文章思路代码 279.完全平方数文章思路代码 总结 70.爬楼梯 文章 代码随想录|0070.爬楼梯完全背包版本 思路 将楼梯长度视为背…...

Apache Doris 2.0 如何实现导入性能提升 2-8 倍

数据导入吞吐是 OLAP 系统性能的重要衡量标准之一,高效的数据导入能力能够加速数据实时处理和分析的效率。随着 Apache Doris 用户规模的不断扩大, 越来越多用户对数据导入提出更高的要求,这也为 Apache Doris 的数据导入能力带来了更大的挑战…...

RabbitMQ: topic 结构

生产者 package com.qf.mq2302.topic;import com.qf.mq2302.utils.MQUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection;public class Pubisher {public static final String EXCHANGE_NAME"mypubilisher";public static void ma…...

信息系统项目管理教程(第4版):第二章 信息技术及其发展

请点击↑关注、收藏,本博客免费为你获取精彩知识分享!有惊喜哟!! 第二章 信息技术及其发展 2.1信息技术及其发展 信息技术是以微电子学为基础的计算机技术和电信技术的结合而形成的,对声音的、图像的、文字的、数字…...

有哪些适合初学者的编程语言?

C语言 那为什么我还要教你C语言呢?因为我想要让你成为一个更好、更强大的程序员。如果你要变得更好,C语言是一个极佳的选择,其原因有二。首先,C语言缺乏任何现代的安全功能,这意味着你必须更为警惕,时刻了…...

uni-app动态tabBar,根据不同用户展示不同的tabBar

1.uni框架的api实现 因为我们用的是uni-app框架开发,所以在创建项目的时候直接创建uni-ui的项目即可,这个项目模板中自带了uni的一些好用的组件和api。 起初我想着这个效果不难实现,因为官方也有api可以直接使用,所以我最开始尝试…...

手写Spring:第6章-资源加载器解析文件注册对象

文章目录 一、目标:资源加载器解析文件注册对象二、设计:资源加载器解析文件注册对象三、实现:资源加载器解析文件注册对象3.1 工程结构3.2 资源加载器解析文件注册对象类图3.3 类工具类3.4 资源加载接口定义和实现3.4.1 定义资源加载接口3.4…...

Redis 7 第八讲 集群模式(cluster)架构篇

集群架构 Redis 集群架构图 集群定义 Redis 集群是一个提供在多个Redis节点间共享数据的程序集;Redis集群可以支持多个master 应用场景 Redis集群支持多个master,每个master又可以挂载多个slave读写分离支持数据的高可用支持海量数据的读写存储操作集群自带Sentinel的故障…...

【PowerQuery】导入与加载XML

在标准数据格式类型里面,有一类比较特殊的数据类型,就是层次结构数据。层次结构数据和标准的结构型数据方式完全不同,在实际应用过程中使用最为频繁的几种数据类型如下。 XML数据格式Json 数据格式Yaml 数据格式我们将在本节和大家一起分享下XML格式数据集成,下一节和大家分…...

vue 预览视频

1.预览本地文件 1.1 直接给video或者embed的src赋值本地路径 <video :src"videoUrl"></video> // 或者 使用embed标签<embed :src"videoUrl" /> 1.2 读取文件流形式 <input type"file" ref"file" /> <vi…...

4个维度讲透ChatGPT技术原理,揭开ChatGPT神秘技术黑盒!(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…...

【无标题】@Scheduled 的cron

, &#xff1a;指定多个值。 -&#xff1a;表示一个区间。 / &#xff1a;指定一个值的增加幅度。n/m表示从n开始&#xff0c;每次增加m。 L&#xff1a;是last的缩写&#xff0c;表示最后一天&#xff0c;用在日表示一个月中的最后一天&#xff0c;用在周表示每周最后一天&…...

IP和MAC的作用区别

在 IP 地址的上一行是 link/ether fa:16:3e:c7:79:75 brd ff:ff:ff:ff:ff:ff&#xff0c;这个被称为 MAC 地址&#xff0c;是一个网卡的物理地址&#xff0c;用十六进制&#xff0c;6 个 byte 表示。 一个网络包要从一个地方传到另一个地方&#xff0c;除了要有确定的地址&…...

python趣味编程-数独游戏

数独游戏是一个用Python编程语言编写的应用程序。该项目包含可以显示实际应用程序的基本功能。该项目可以让修读 IT 相关课程并希望开发简单应用程序的学生受益。这个Python 数独游戏是一个简单的项目,可用于学习tkinter库的实践。这个数独游戏可以提供Python编程的基本编码技…...

MySQL/MariaDB 查询某个 / 多个字段重复数据

创建测试表和数据 # 创建表 create table if not exists t_duplicate (name varchar(255) not null,age int not null );# 插入测试数据 insert into t_duplicate(name, age) values(a, 1); insert into t_duplicate(name, age) values(a, 2);查询单个字段重复 使用 count() …...

【力扣每日一题】2023.9.10 课程表Ⅱ

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 今天的题目和昨天类似&#xff0c;不过今天要我们求出学习所有课程的先后顺序。 昨天只需要我们求出能否学习完所有课程&#xff0c;因此…...

咸阳微网站建设/seo网站诊断分析报告

1.Fedora 15使用root登录gnome 首先以用户账号进入终端&#xff0c;允许root账号登陆&#xff1a;$ su$ vi /etc/pam.d/gdm删除其中的一行 auth required pam_succeed_if.souser ! root quiet:wq保存并退出。$ vi /etc/pam.d/gdm-password删除其中的一行 auth required pam_suc…...

白酒网站定制开发/2022年新闻摘抄简短

无需打开APP&#xff0c;甚至不用亮屏&#xff0c;手机轻轻一靠&#xff0c;公交、地铁出行无忧&#xff01;江苏一卡通联合南京市民卡、南京联通近日发行手机公交卡“金陵通沃卡”&#xff0c;为安卓和苹果两大系统的主流机型用户打开便捷出行的新途径。作为交通运输部首批全国…...

做公司网站需要几个域名/百度访问量统计

目录第一章 AngularJS简介1.1、AngularJS简介1.2、AngularJS版本介绍1.3、AngularJS官方地址1.4、AngularJS代码体验第二章 AngularJS方法2.1、字符串大小写转换2.2、对象/数组深度拷贝2.3、对象/数组迭代函数2.4、常见数据类型判断2.5、序列化与反序列化第三章 AngularJS指令3…...

b2c网站推广方案/全国十大跨境电商排名

在炼丹的过程中&#xff0c;学习率的调整是必不可少的&#xff0c;下面给出scheduler模块的调学习率的方法&#xff0c;后面会慢慢补充 调整学习率&#xff1a;PyTorch官方文档 一、CyclicLR torch.optim.lr_scheduler.CyclicLR(optimizer, base_lr, max_lr, step_size_up20…...

如何分析企业网站/免费外链发布平台在线

如果再不写点东西&#xff0c;害怕自己以后再也不会写程序了&#xff0c;很久没有更新BLOG了&#xff0c;作为即将失去九月的几年还是摘录一篇稿子中的东西吧——关于Command对象的&#xff0c;主要是关于参数的问题&#xff0c;Command对象参数输入输出&#xff0c;以及获取的…...

乳山网站建设/小熊猫seo博客

1、在/usr/share/applications创建一个名为“eclipse.desktop”的文件 具体命令为&#xff1a; gedit eclipse.desktop 并添加以下内容&#xff1a; [Desktop Entry] EncodingUTF-8 NameEclipse Platfrom CommentEclipse IDE Exec/home/lgh/Desktop/eclipse/eclipse…...