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

news2025/4/6 16:58:48

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

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

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

相关文章

JWT解决跨域问题详解

介绍 在前后端不分离时,我们利用前面讲过的Spring Security的各种知识点,就可以实现对项目的权限管控。但是在前后端分离时,尤其是在引入了Spring Security后的前后端分离时,我们从前端发来的请求,就会存在一些问题。…

IBM Spectrum LSF Process Manager —— 设计、记录和运行复杂的计算工作流

亮点 ● 快速创建复杂的分布式工作流 ● 开发可重复的最佳实践 ● 自信地运行关键工作流程 ● 提高流程可靠性 IBM Spectrum LSF Process Manager 使您能够设计和自动化计算或分析流程, 捕获和保护可重复的最佳实践。 使用直观的图形界面…

时序预测 | MATLAB实现PSO-LSSVM粒子群算法优化最小二乘支持向量机时间序列预测未来

时序预测 | MATLAB实现PSO-LSSVM粒子群算法优化最小二乘支持向量机时间序列预测未来 目录 时序预测 | MATLAB实现PSO-LSSVM粒子群算法优化最小二乘支持向量机时间序列预测未来预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.Matlab实现PSO-LSSVM时间序列预测未…

Springboot 实践(13)spring boot 整合RabbitMq

前文讲解了RabbitMQ的下载和安装&#xff0c;此文讲解springboot整合RabbitMq实现消息的发送和消费。 1、创建web project项目&#xff0c;名称为“SpringbootAction-RabbitMQ” 2、修改pom.xml文件&#xff0c;添加amqp使用jar包 <!-- RabbitMQ --> <dependency&g…

如何确认this的值

<script>/*如何确认this的值1.全局执行环境严格模式&#xff0c;非严格模式&#xff1a;全局对象window2.函数内部环境2.1直接调用严格模式下&#xff1a;undefined非严格模式&#xff1a;全局对象&#xff08;window&#xff09;2.2对象方法调用严格模式&#xff0c;非严…

OCR扫描仪选购技巧,轻松将纸质文件转为电子文本

OCR&#xff08;光学字符识别&#xff09;扫描仪是一种特殊扫描仪&#xff0c;能够将纸质文件上的文字转换为可编辑的电子文本。在选购OCR扫描仪时&#xff0c;有一些技巧可以帮助我们选择到适合自己需求的产品。以下介绍几个简单实用的OCR扫描仪选购技巧。 首先&#xff0c;需…

使用pip下载第三方软件包报错超时处理方法

报错如下&#xff1a; WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by ‘ReadTimeoutEr ror(“HTTPSConnectionPool(host‘files.pythonhosted.org’, port443): Read timed out. (read timeout15)”)’: /p…

【AGC】云数据库API9开发问题汇总

【问题描述】 云数据库HarmonyOS API9 SDK已经推出了一段时间了&#xff0c;下面为大家汇总一些在集成使用中遇到的问题和解决方案。 【问题分析】 1. 报错信息&#xff1a;数据库初始化失败&#xff1a;{“message”&#xff1a;“The object type list and permission …

开启Clash和系统代理后Chrome无法打开网页但Edge正常

今天早上打开电脑准备摸鱼&#xff0c;发现Chrome打不开网页了。检查Clash正常&#xff0c;切换了节点&#xff0c;依然不行。关闭系统的代理可以解决。不然只提示ERR_TIMED_OUT。 各种研究配置&#xff0c;然后发现Edge却又不受影响。 通过火绒发现Chrome是有连接到7890端口的…

【LeetCode】328. 奇偶链表

328. 奇偶链表&#xff08;中等&#xff09; 思路 如果链表为空&#xff0c;则直接返回链表。 对于原始链表&#xff0c;每个节点都是奇数节点或偶数节点。头节点是奇数节点&#xff0c;头节点的后一个节点是偶数节点&#xff0c;相邻节点的奇偶性不同。因此可以将奇数节点和偶…

Typora for Mac(Markdown文本编辑器)

Typora是一款简洁、直观的跨平台Markdown编辑器软件。它提供了一个非常直观和简单的界面&#xff0c;让用户可以更轻松地编写和编辑Markdown语言的文档。 Typora具有实时预览功能&#xff0c;这意味着你可以在编辑Markdown文档的同时立即看到最终的效果。它允许用户快速切换编辑…

简化车辆登记流程:利用腾讯云OCR实现自动化信息识别

项目中有一块&#xff0c;需要用到上传车牌车牌号到系统里&#xff0c;用了下腾讯云的ocr车牌号识别做了个小功能。通过腾讯云的orc识别&#xff0c;将车牌号录入到后台。 一&#xff0c;首先我们需要登录到腾讯云&#xff0c;然后搜索一下orc或者车牌号 https://curl.qcloud.…

一阶多智能体的平均一致性

数学表达 一阶多智能体的运动学方程可以描述为 x ˙ i ( t ) u i ( t ) , i ∈ { 1 , 2 , 3 , … , N } \dot x_i(t) u_i(t),i\in\{1,2,3,\dots,N\} x˙i​(t)ui​(t),i∈{1,2,3,…,N} 其中 x i ( t ) x_i(t) xi​(t)为状态&#xff0c; u i ( t ) u_i(t) ui​(t)为控制量&…

SIP mini 对讲终端,带sip热点功能

SV-A10/SV-A10W SIP mini 对讲终端&#xff0c;带sip热点功能 SV-A10/SV-A10W 是专门针对行业用户需求研发的一款 SIP mini 对讲产品&#xff0c;外观小巧&#xff0c;功能 强大&#xff0c;集智能安防、音/视频对讲和广播功能于一体&#xff0c;性价比高。支持壁挂式安装/86…

lv3 嵌入式开发-4 linux shell命令(文件搜索、文件处理、压缩)

目录 1 查看文件相关命令 1.1 常用命令 1.2 硬链接和软链接 2 文件搜索相关命令 2.1 查找文件命令 2.2 查找文件内容命令 2.3 其他相关命令 3 文件处理相关命令 3.1 cut 3.2 sed 过滤 3.3 awk 匹配 4 解压缩相关命令 4.1 解压缩文件的意义 4.2 解压缩相关命令 1 …

C++ | 负数比0大?

C | 负数比0大&#xff1f; 文章目录 C | 负数比0大&#xff1f;现象分析剖析赋值运算类型提升标准有符号整数类型的转换级别关系为&#xff1a;char short转换int long double 转换 >>>>> 欢迎关注公众号【三戒纪元】 <<<<< 现象 在 for 的判断…

气传导耳机哪个牌子好?2023气传导耳机排行榜推荐

​在众多的气传导耳机中&#xff0c;如何选择一款适合自己的气传导耳机呢&#xff1f;这需要考虑到自己的需求和预算&#xff0c;同时也需要了解不同品牌和型号的产品特点和优缺点。下面我来推荐几款非常不错的气传导耳机给大家参考&#xff0c;希望大家都能寻找到心仪那款。 …

深入理解联邦学习——联邦学习的价值

分类目录&#xff1a;《深入理解联邦学习》总目录 毫无疑问&#xff0c;如今我们正经历互联网第四次信息革命&#xff0c;坐拥海量的信息与数据。这些数据如果能够用AI的方式进行解读&#xff0c;将会为人类日常生活带来颠覆性变革。联邦学习作为未来AI发展的底层技术&#xff…

C++ 多态语法点

前置知识点 成员变量和成员函数分开存储&#xff0c;只有非静态成员变量才属于类的对象上。 静态成员变量和静态成员函数没有在类上存储。 非静态成员函数也不属于类的对象上 class Animal {public:virtual void speak(){cout<<"动物在说话"<<endl;}}v…

git快速使用

1、下载git 设置签名 2、基本概念 工作区&#xff1a;写代码的地方。 暂存区&#xff1a;.git的.index 工作区&#xff1a;.git 3、常用操作 本地codinggit init&#xff0c; 初始化一个本地仓库&#xff0c;项目根目录下会出现个.gitgit remote add origin gitgithub.com…