【面试题】对async/await 的了解?

news2025/1/11 20:00:49

前言

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库


“编程,就像一场开卷考试,题目的答案取决于对书本内容的熟悉程度;而一份源代码就好比一本书,正所谓读书百遍其义自见,读懂源码就读懂了编程!”

Hello,《这就是编程》每周六晚如期更新,带大家走进编程世界,一起读懂源码,读懂编程!

Error-First Callback风格的异步代码容易出现Callback Hell,Promise的出现某种意义上解决了这个问题,但是期待一种同步风格来编写异步代码。

随着JavaScript语言特性开始支持async function,async/await可以达到同步代码风格,这种“同步”方式是如何实现的呢?今天第二期,带来关于在cxx层面async function实现原理的源码分析,让你进一步提升对JavaScript异步编程的认识,以及掌握async/await使用细节。

假如读者想对Promise实现原理做进一步了解,可以查看上一期内容👆🏻,因为Promise是async/await的基础。

执行过程


JavaScript是解释型语言,如上图,V8经过AST parser和Bytecode-Generator得到JavaScript与机器码的中间产物bytecode,bytecode展示了JavaScript执行过程。

全文以下面这段代码为例,分析在V8中async function如何执行。

asyncfunctionfoo(v) {
    const w = await v;
    return w;
}

foo(42)
    .then((res) => { console.log(res) })
    .catch((err) => { console.log(err) })
复制代码

首先看一下foo函数声明时生成的bytecode

[generated bytecode for function:(0x0efd0025a71d<SharedFunctionInfo>)]
Bytecode length:56Parametercount1Registercount5Framesize40Bytecode age:00xefd0025a85e@0 :1300LdaConstant [0]
         0xefd0025a860@2 :c3Star10xefd0025a861@3 :19fef8Mov<closure>,r20xefd0025a864@6 :656401f902CallRuntime [DeclareGlobals],r1-r20xefd0025a869@11 :210100LdaGlobal [1], [0]
         0xefd0025a86c@14 :c1Star30xefd0025a86d@15 :0d2aLdaSmi [42]
         0xefd0025a86f@17 :c0Star40xefd0025a870@18 :62f7f602CallUndefinedReceiver1r3,r4, [2]
         0xefd0025a874@22 :c1Star30xefd0025a875@23 :2df70204GetNamedPropertyr3, [2], [4]
         0xefd0025a879@27 :c2Star20xefd0025a87a@28 :80030000CreateClosure [3], [0],#00xefd0025a87e@32 :c0Star40xefd0025a87f@33 :5ef8f7f606CallProperty1r2,r3,r4, [6]
         0xefd0025a884@38 :c2Star20xefd0025a885@39 :2df80408GetNamedPropertyr2, [4], [8]
         0xefd0025a889@43 :c3Star10xefd0025a88a@44 :80050100CreateClosure [5], [1],#00xefd0025a88e@48 :c1Star30xefd0025a88f@49 :5ef9f8f70aCallProperty1r1,r2,r3, [10]
         0xefd0025a894@54 :c4Star00xefd0025a895@55 :a9ReturnConstantpool(size=6)0xefd0025a81d: [FixedArray] inOldSpace-map:0x0efd00000089<Map(FIXED_ARRAY_TYPE)>-length:60:0x0efd0025a765<FixedArray[2]>1:0x0efd0025a6d5<String[3]:#foo>2:0x0efd0000605d<String[4]:#then>3:0x0efd0025a7ad<SharedFunctionInfo>4:0x0efd0020378d<String[5]:#catch>5:0x0efd0025a7e5<SharedFunctionInfo>HandlerTable(size=0)SourcePositionTable(size=0)复制代码

从bytecode中发现,async function的声明过程和普通函数一样,毕竟async function是以Function.prototype为原型。

await实现原理

重点分析一下对应foo函数执行的bytecode,如下:

[generated bytecode for function:foo(0x0efd0025a775<SharedFunctionInfofoo>)]
Bytecode length:87Parametercount2Registercount6Framesize48Bytecode age:00xefd0025a9fe@0 :aefa0001SwitchOnGeneratorStater0, [0], [1] { 0:@33 }
         0xefd0025aa02@4 :19fef8Mov<closure>,r20xefd0025aa05@7 :1902f7Mov<this>,r30xefd0025aa08@10 :6802f802InvokeIntrinsic [_AsyncFunctionEnter],r2-r30xefd0025aa0c@14 :c4Star00xefd0025aa0d@15 :19fff8Mov<context>,r20xefd0025aa10@18 :19faf7Movr0,r30xefd0025aa13@21 :1903f6Mova0,r40xefd0025aa16@24 :6801f702InvokeIntrinsic [_AsyncFunctionAwaitUncaught],r3-r40xefd0025aa1a@28 :affafa0300SuspendGeneratorr0,r0-r2, [0]
         0xefd0025aa1f@33 :b0fafa03ResumeGeneratorr0,r0-r20xefd0025aa23@37 :c1Star30xefd0025aa24@38 :680bfa01InvokeIntrinsic [_GeneratorGetResumeMode],r0-r00xefd0025aa28@42 :c0Star40xefd0025aa29@43 :0cLdaZero0xefd0025aa2a@44 :1cf6TestReferenceEqualr40xefd0025aa2c@46 :9805JumpIfTrue [5] (0xefd0025aa31@51)0xefd0025aa2e@48 :0bf7Ldarr30xefd0025aa30@50 :a8ReThrow0xefd0025aa31@51 :19f7f9Movr3,r10xefd0025aa34@54 :19faf7Movr0,r30xefd0025aa37@57 :19f9f6Movr1,r40xefd0025aa3a@60 :6804f702InvokeIntrinsic [_AsyncFunctionResolve],r3-r40xefd0025aa3e@64 :a9Return0xefd0025aa3f@65 :c1Star30xefd0025aa40@66 :82f701CreateCatchContextr3, [1]
         0xefd0025aa43@69 :c2Star20xefd0025aa44@70 :10LdaTheHole0xefd0025aa45@71 :a6SetPendingMessage0xefd0025aa46@72 :0bf8Ldarr20xefd0025aa48@74 :1af7PushContextr30xefd0025aa4a@76 :1702LdaImmutableCurrentContextSlot [2]
         0xefd0025aa4c@78 :bfStar50xefd0025aa4d@79 :19faf6Movr0,r40xefd0025aa50@82 :6803f602InvokeIntrinsic [_AsyncFunctionReject],r4-r50xefd0025aa54@86 :a9ReturnConstantpool(size=2)0xefd0025a9cd: [FixedArray] inOldSpace-map:0x0efd00000089<Map(FIXED_ARRAY_TYPE)>-length:20:331:0x0efd0025a99d<ScopeInfoCATCH_SCOPE>HandlerTable(size=16)fromtohdlr(prediction,data)(18,65)->65(prediction=3,data=2)SourcePositionTable(size=0)复制代码

对照如下执行示意图:

  • 首先async function编译后被标记成resumable,也就是byecode中SwitchOnGeneratorState,因为async/await底层是以Generator为基础封装,也是借由Generator的协程机制实现可挂起、可恢复

  • 接着InvokeIntrinsic [_AsyncFunctionEnter],async function开始执行,在[AsyncFunctionEnter](https://github.com/v8/v8/blob/main/src/builtins/builtins-async-function-gen.cc#L79)里,核心逻辑包括计算寄存器及参数数量、创建寄存器、创建需返回调用者的promise实例以及创建代表async function自身的async_function_object对象

  • 然后InvokeIntrinsic [_AsyncFunctionAwaitUncaught],即是进入await语法糖处理逻辑,以Promise实例包装每一个await point,并在handlers里封装resume/throw处理逻辑,以实现在特定执行时机(比如promise fulfilled)恢复且返回结果值

说到这里,先穿插分析下await语法糖的实现原理,V8在生成bytecode的过程中会经过很多的visitor,对于await关键字会进入BytecodeGenerator::VisitAwait处理

在VisitAwait里,对当前await point保存现场,然后对其进行BuildAwait,使得每一个await point成为resumable,即可挂起和可恢复,具体看看BuildAwait是如何处理:

  1. 首先为register寄存器设置正确的作用域,因为这关系到await point在LoadAccumulatorWithRegister时能否从正确的register里获取结果值,即在JavaScript层面await能否获取到预期结果值

  1. 确定await关键字的FunctionId,这个FunctionId直接决定了await在cxx层面的逻辑处理,即怎么处理跟随的“数据”。上面bytecode里调用的InvokeIntrinsic [_AsyncFunctionAwaitUncaught]正是FunctionId所指向的cxx内部AsyncFunctionBuiltinsAssembler的built-in方法AsyncFunctionAwaitUncaught

 0xefd0025aa16 @   24 : 68 01 f7 02       InvokeIntrinsic [_AsyncFunctionAwaitUncaught], r3-r4
复制代码

  1. 绑定FunctionId作为await的逻辑处理函数,然后获取register列表,并作为闭包参数传给await使用

接着BuildSuspendPoint

为await point标记suspend_id构建suspend point,使得await point可被挂起。suspend_id是suspend point的唯一索引,缓存在generator_jump_table_中。等await触发时,在suspend_id位置挂起async_function_object;await处理完成时从suspend_id对应await point恢复继续执行。

0xefd0025aa1a@28 :affafa0300SuspendGeneratorr0,r0-r2, [0]
 0xefd0025aa1f@33 :b0fafa03ResumeGeneratorr0,r0-r2复制代码
  1. 最后判断当前async_function_object是否需要resume,resume_mode为true,表明async function需要从suspend_id位置ResumeGenerator恢复继续执行,则将async_function_object的当前value返回给当前await point,进入InvokeIntrinsic [[_AsyncFunctionResolve](https://github.com/v8/v8/blob/main/src/builtins/builtins-async-function-gen.cc#L179)];否则ReThrow抛错退出进入InvokeIntrinsic [[_AsyncFunctionReject](https://github.com/v8/v8/blob/main/src/builtins/builtins-async-function-gen.cc#L156)]

0xefd0025aa1f@33 :b0fafa03ResumeGeneratorr0,r0-r20xefd0025aa23@37 :c1Star30xefd0025aa24@38 :680bfa01InvokeIntrinsic [_GeneratorGetResumeMode],r0-r00xefd0025aa28@42 :c0Star40xefd0025aa29@43 :0cLdaZero0xefd0025aa2a@44 :1cf6TestReferenceEqualr40xefd0025aa2c@46 :9805JumpIfTrue [5] (0xefd0025aa31@51)复制代码

以上bytecode非常清晰展示了async function整个执行过程。

再以直观的示意图来加深印象:

注意:
V8 v7.2之前,无论v值类型都会为其创建临时promise实例

由图中分析:

  1. 创建implicit_promise作为async function返回值,同时createPromise创建临时promise实例用于对v值(命名为promise_42)进行resolvePromise

  1. 针对每一个await point,首先通过performPromiseThen将async function的resume/throw注册到promise_42。然后suspend挂起,等待promise_42处理。一旦fulfilled执行resume并将数据返回await point,进入下一个await point;而rejected就执行throw退出async function。

  1. 最后async function并将implicit_promise返回给调用者,同时归还执行权,调用者继续执行后续script代码,如对implicit_promise进行then方法调用等。

  1. foo函数等待ResolvePromise(promise, promise_42),此时由于ResolvePromise入参promise_42是一个promise实例,会创建

PromiseResolveThenableJob

放入microtask队列等待,而PromiseResolveThenableJob又会创建PromiseReactionJob

V8 v7.2之后,根据v值类型判断是否创建临时Promise实例

图左红底代码块逻辑事实上等价于PromiseResolve

如图右绿底代码,利用PromiseResolvebuilt-in实现,当v值是promise实例时直接复用,即不再创建临时promise实例,这样避免ResolvePromise入参为promise实例而额外创建PromiseResolveThenableJob,而是直接创建PromiseReactionJob。

在最后一个await point接收到从async function resume的返回值42时,由所有await point构建的promise chain进入最后一跳,也就是implicit_promise。async function成功退出,对应implicit_promise进入fulfilled;而如果因任一await point抛错导致的失败退出,对应implicit_promise就进入了rejected,调用者通过then注册的对应状态handler也会执行。

await如何执行

上面分析了await关键字的实现原理,接下来要分析一下await关键字如何执行,也就是cxx层面的built-in方法AsyncFunctionBuiltinsAssembler::AsyncFunctionAwait

首先着重看看注释部分,表达的信息很重要:

  • AsyncFunctionAwait是await关键字的核心逻辑,V8将await value语法糖反解成yield AsyncFunctionAwait(.generator_object, value),表明async function在cxx层面确实是以Generator为基础而封装实现的

OK,接着看一下AsyncFunctionAwait的具体处理逻辑:

  • 首先Return(outer_promise)表明AsyncFunctionAwait返回promise实例,就像foo函数例子里的implicit_promise一样。本质上,从下面这段代码可以发现outer_promise是贯穿于async function的整个执行过程的,简而言之,每一个await point都与这个outer_promise关联。

TNode<JSPromise> outer_promise = LoadObjectField<JSPromise>(
       async_function_object, JSAsyncFunctionObject::kPromiseOffset)
  • 然后重点要提到AsyncFunctionAwaitResolveSharedFunConstant和AsyncFunctionAwaitRejectSharedFunConstant,由它们生成Promise层面的resolve/reject方法对

在所有准备工作完成之后,await就进入真正的处理逻辑AsyncBuiltinsAssembler::Await

首先针对await接收的value值进行PromiseResolve处理,最后得到一个wrapper promise实例。

  • 接着初始化await的上下文,这里的上下文不是async function对应的JSAsyncFunction实例async_function_object,可以理解是接下来马上要创建的onFulfilled/onRejected handlers的闭包上下文

  • 同时因为在闭包上下文中无法获取到async_function_object,所以要将它存入闭包上下文的扩展字段,这样onFulfilled/onRejected handlers执行时就能获取到正确的async_function_object,也就能对其进行resume/throw操作

接下来就是创建onFulfilled/onRejected handlers,在async function里,它们包含了resume/throw执行逻辑,通过它们就能从suspend point恢复或者抛错退出。

resume/throw对应bytecode中ResumeGenerator/Rethrow

其实整个await执行过程,除了在BuildAwait时BuildSuspendPoint引入了suspend point,其他就是纯粹的Promise处理逻辑。接下来就是对wrapper promise实例调用一次PerformPromiseThenImpl,如下代码所示,而此时也会传递上面创建的onFulfilled/onRejected handlers。最后一旦onFulfilled,async function就从await point恢复继续执行,然后继续后面的await point的执行;而一旦onRejected,async function就会在suspend point抛错退出

performPromiseThen(
    promise,
    (res) => resume(<<foo>>, res),
    (err) => throw(<<foo>>, err),
    throwaway
)
复制代码
<<foo>>就是async_function_object,作为resume/throw的执行上下文

注意:

事实上,resume/throw在cxx层面是由generator的next/throw来实现,且async function里的return对应了Generator.prototype.return,本质上也是一次await point处理,只不过在return时,generator实例的next方法返回值中done属性为true,表示generator迭代完结,从而停止触发next方法。

小结

OK,目前已经分析了async function在V8中整个执行过程,总结起来它的实现原理是:

  1. 以Generator协程机制为基础,由Promise实现异步执行

  1. 通过promise chain搭配Generator的resume/rethrow达到await“等待”执行的同步效果

相信大家对async/await也有了新的认识,接下来继续来看一些和JavaScript async function有关的优秀源码,比如regenerator-runtime。

regenerator-runtime


在JavaScript业界,提到async/await,就不得不提regenerator-runtime,它是facebook/regenerator中关于async function的runtime实现。

鉴于当前兼容性问题,async function有时不能以原生的方式使用,因此会使用regenerator-runtime来模拟async function的runtime。

facebook/regenerator是一个monorepo,不仅包括regenerator-runtime,也提供了regenerator命令,通过regenerator --include-runtime可以生成自带regenerator-runtime的async function转译代码,如转译后的foo函数,这里省略了regenerator-runtime代码:

functionfoo() {  
    var v;  
    return regeneratorRuntime.async(functionfoo$(_context) {    
        while (1) switch (_context.prev = _context.next) {
            case0:       
                _context.next = 2;        
                return regeneratorRuntime.awrap(42);      
            case2:        
                v = _context.sent;        
                return _context.abrupt("return", v);      
            case4:      
            case"end":        
                return _context.stop();    
        }  
    }, null, null, null, Promise);
}
复制代码

发现async function被转译成regeneratorRuntime.async,同时async function的函数体变成了while(1)无限循环。

往往regenerator转译后的代码给人的印象,甚至说误解:

  • 调用栈紊乱,难以调试,尤其是while(1)无限循环的出现

  • 增加代码大小影响性能

但希望在明白它的实现原理之后能缓解以上这些误解

Generator runtime

之所把它放在async function在V8的实现原理分析之后,是因为regenerator-runtime完美复刻了V8的async function实现原理:

  1. 以Generator协程机制为基础,由Promise实现异步执行

  1. 通过promise chain搭配Generator的resume/rethrow达到await“等待”执行的同步效果

  1. 首先获取AsyncIterator实例iter

  1. 通过promise chain配合iter.next来实现await“等待”效果,因为当AsyncIterator迭代器未完结时就会由promise onFulfilled触发下一次iter.next继续迭代下去

  1. 最后将promise chain返回给调用者

wrap函数的目的就如其字面意思一样,包装generator实例,同时定义一个_invoke作为Generator原生方法next、return、throw的泛化调用。

在AsyncIterator构造器里:

  • 将包装的generator实例封装进AsyncIterator实例作为协程机制的基础触发点。

  • 定义一个_invoke属性,以enqueue函数作为属性值,enqueue的作用是始终返回一个promise实例,并同时触发generator持续迭代,以promise chain来响应generator的迭代值或抛错。

Suspend point

经过以上分析明白了regenerator-runtime内部如何通过Generator配合Promise来模拟await,但是对于调用者,是怎么在所谓的await point位置获取正确的结果值呢?

这一切都归功于Context实例,它巧妙的模拟了suspend point

var context = new Context(tryLocsList || []);复制代码

在V8执行async function时,对每一个await point都会构建一个suspend point并设置对应的suspend_id;而在regenerator-runtime中,所有的suspend point都在while(1)中的switch里,每一个case就是一个suspend point,准确的说,这些suspend point是在regenerator的ast解析过程中生成的,而所有的suspend_id对应await point的ast node的index索引值。

对于await point,当generator实例正确执行next/return时,suspend point处恢复的执行权按顺序从上往下通过context.next来传递,而当generator实例throw抛错时,则在context里throw context.arg退出并进入promise chain的onRejected handler。

现在理解了regenerator-runtime的实现原理,后续调试肯定会得心应手了。

结尾


经过上一期《这就是编程:你知道在cxx语言层面Promise如何实现吗》和本期对async function在cxx层面的实现原理分析,相信对JavaScript异步编程肯定有了更深的理解:

  • 了解了await语法糖如何实现

  • 了解了async function在V8内部以Generator协程机制为基础

  • 了解了async function如何通过promise chain巧妙实现await“等待”效果

OK,本期先分享到这里,如果读者有任何疑惑,欢迎评论区留言讨论👏🏻,笔者很乐意与大家分享源码阅读的心得,也是一次很好的自我学习机会🤔。

大厂面试题分享 面试题库

后端面试题库 (面试必备) 推荐:★★★★★

地址:前端面试题库

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

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

相关文章

源码才十几行的数组转换工具arrify,快学起来

前言 前几天在项目中运用到了arrify工具&#xff0c;今天便阅读了arrify的源码&#xff0c;整个源码部分不过才十几行&#xff0c;而且也不难&#xff0c;所以来分享一下阅读心得。 arrify介绍 arrify是什么&#xff0c;有什么作用&#xff0c;或许有些小伙伴还不清楚。简单…

web测试的基本流程

1、web测试流程&#xff1a; (1)web测试 1)参与一个web新项目的测试前&#xff0c;先搜集测试相关的资料&#xff0c;包括原型图、各种需求文档、业务相关等需求相关材料 2)结合第一步搜集到的需求相关资料&#xff0c;自行熟悉系统&#xff0c;同时列出不明白的点&#xff0c;…

时间API在更新,传奇已经谢幕,但技术永远不死

&#xff08;Bill Joy(左一)&#xff0c;Vinod Khosla(左二)&#xff0c;Andy Bechtolsheim(右二)&#xff0c;Scott McNealy(右一) &#xff09; CSDN 博文征集活动&#xff08;和日期相关的代码和bug&#xff09;&#xff1a;点击这里 各位 “big guys”&#xff0c;这篇博文…

植物大战 二叉搜索树——C++

这里是目录标题二叉排序树的概念模拟二叉搜索树定义节点类insert非递归Finderase(重点)析构函数拷贝构造(深拷贝)赋值构造递归FindRInsertR二叉搜索树的应用k模型KV模型二叉排序树的概念 单纯的二叉树存储数据没有太大的作用。 搜索二叉树作用很大。 搜索二叉树的一般都是用…

摸鱼用python获取弹幕的两种方式【前者简单,后者数据好看】

嗨害大家好鸭&#xff01;我是小熊猫~ 相信大家对于 “弹幕文化” 已经相当熟悉啦 你不是一个人在看——这就是弹幕网站的存在感。 它形成了新的“抱团”观看模式&#xff0c; 也真正实现了无时空距离的社交。 有网友表示&#xff0c;弹幕简直比剧情还有趣。 看似简单的寥寥…

【ES】Elasticsearch-深入理解索引原理

文章目录Elasticsearch-深入理解索引原理读操作更新操作SHARD不变性动态更新索引删除和更新实时索引更新持久化Segment合并近实时搜索&#xff0c;段数据刷新&#xff0c;数据可见性更新和事务日志更新索引并且将改动提交修改Searcher对象默认的更新时间Elasticsearch-深入理解…

CentOS8基础篇9:进程的延迟与周期调度

一、进程的概念 进程&#xff1a;开始执行但是还没有结束的程序的实例 程序&#xff1a;包含可执行代码的文件 进程与程序的关系 进程由程序产生&#xff0c;是一个运行着的、要占系统资源的程序 进程不等于程序 浏览网络时&#xff0c;打开多个IE浏览器程序&#xff1b;…

一文讲清楚如何进行主数据编码

主数据编码作为一类重要的数据资源&#xff0c;在信息化建设中具有重要的地位和作用&#xff0c;是保证现有信息系统和未来新系统建设成功的关键因素&#xff0c;决定着系统中的信息一致性。 编码&#xff0c;是一件简单的事情&#xff0c;但绝对不是一件容易做好的事情&#…

FPGA案例开发手册——基于全志T3+Logos FPGA核心板

前 言 本文档主要提供评估板FPGA端案例测试方法,适用的开发环境为Windows 7 64bit和Windows 10 64bit。 本文案例基于创龙科技的全志T3+Logos FPGA核心板,它是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计的异构多核全国产工业核心板…

PImpl(Pointer to Implementation)指向实现的指针 [使用ChatGPT学习系列]

PImpl是Pointer to Implementation的缩写&#xff0c;也被称为“编译期实现”&#xff0c;是一种C设计的模式。 用于将类的实现细节与其公共接口分离开来。该模式的核心思想是 通过一个指向类的实现的指针来隐藏类的实现细节&#xff0c;从而提高类的封装性和安全性。PImpl是一…

「考研算法」

考研算法 前言 本系列文章涉及的算法内容&#xff0c;针对的是哈尔滨工业大学854科目。在本文中通过具体的算法题进行讲解相应算法。 今天涉及的算法主要有线性筛&#xff0c;十大排序中快速排序和归并排序。 后续会有动态规划的相关算法以及尝试模型的总结&#xff0c;如果…

[Java·算法·中等]LeetCode17. 电话号码的字母组合

每天一题&#xff0c;防止痴呆题目示例分析思路1题解1分析思路2题解2题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。…

本科毕业设计-基于ORB SLAM3的多从机SLAM导航系统

耗时&#xff1a;两个月 需求&#xff1a;多从机协作 多地图系统 稠密建图 定位 导航 硬件&#xff1a;二个D435 一台X86主机&#xff08;CPU:13600kf 内存:32G&#xff09; X86主机环境&#xff1a;ubuntu18.04 opencv3.2 ROS1 主要代码参考&#xff1a;ORB-SLAM3 主要调用…

【CNN】FractalNet——与DenseNet有异曲同工之妙

FractalNet论文名称&#xff1a;FractalNet: Ultra-Deep Neural Networks without Residuals FractalNet论文下载链接&#xff1a; FractalNet&#xff08;分型网络&#xff09;&#xff0c;2016年Gustav Larsson首次提出。 &#xff08;1&#xff09;分形网络不像resNet那样…

嵌入式 Linux 文件IO操作

目录 Linux 文件操作 1 Linux 系统环境文件操作概念 2 缓冲 IO 文件操作 1 文件的创建&#xff0c;打开与关闭 fopen 函数函数 2 freopen 函数 3、fdopen函数 4、fclose函数 5、格式化读写 6、单个字符读写 7、文件定位 8、标准目录文件 9、非缓冲IO文件操作 Linux 文…

十二、MyBatis的高级映射及延迟加载

1 数据库表的准备 准备数据库表&#xff1a;一个班级对应多个学生。班级表&#xff1a;t_clazz。学生表&#xff1a;t_stu 2 环境搭建 创建模块 打包方式&#xff1a;jar 引入依赖&#xff1a;mybatis依赖、mysql驱动依赖、junit依赖、logback依赖 配置文件&#xff1a;…

C#/.net程序调用python

C#/.net程序调用python C#的优势在于window下的开发&#xff0c;不仅功能强大而且开发周期短。而python则有众多的第三方库&#xff0c;可以避免自己造轮子&#xff0c;利用C#来做界面&#xff0c;而具体实现使用python来实现可以大大提高开发效率。本文介绍如何使用pythonnet…

Kubernetes初始化容器

初始化容器 之前了解了容器的健康检查的两个探针&#xff1a;liveness probe&#xff08;存活探针&#xff09;和readiness probe&#xff08;可读性探针&#xff09;的使用方法&#xff0c;我们说在这两个探针是可以影响容器的生命周期的&#xff0c;包括我们之前提到的容器的…

如何或者无插件Web页面监控播放软件LiveNVR的固定视频流地址,实现大屏上墙、播放、视频分析等目的

1、LiveNVR介绍 LiveNVR的安防监控的视频直播&#xff0c;可以按标准的Onvif/RTSP协议接入监控设备&#xff0c;也可以通过海康、大华、天地伟业等厂家私有SDK接入监控&#xff0c;实现web页面的播放和录像回放。 可以分发HTTP-FLV、WS-FLV、WebRTC、RTMP、HLS(M3U8)、RTSP等多…

Linux安装Tomcat9

默认Linux已经安装了JDK 并且已经配置好了环境变量 下载链接 Tomcat9 下载完成如下图 &#xff0c;这个下载完成需要看一下&#xff0c;有的包里bin目录内缺少bootstrap.jar文件&#xff0c;因此下载包的时候要看看bin目录下的是不是有这个文件&#xff0c;如果没有启动Tomcat…