Event loop的初步理解
相信大家如果对Event loop有一定了解的话,大概都会知道它的大概步骤是:
- 1,Javascript的事件分为同步任务和异步任务.
- 2,遇到同步任务就放在执行栈中执行.
- 3,遇到异步任务就放到任务队列之中,等到执行栈执行完毕之后再去执行任务队列之中的事件.
上面的步骤说得没错,很对,但是太浅了.现在的面试应该不会这么简单,起码得加上宏任务,微任务.再整上几个Promise,async await,让你判断.否则都不好意思叫面试题!
为了避免被面试官吊起来打的情况,我们现在来详细地理一理Event Loop.
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析
Event loop 相关概念
JS调用栈
Javascript 有一个 主线程(main thread)和 调用栈(call-stack),所有的代码都要通过函数,放到调用栈(也被称为执行栈)中的任务等待主线程执行。
JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。
WebAPIs
MDN的解释: Web 提供了各种各样的 API 来完成各种的任务。这些 API 可以用 JavaScript 来访问,令你可以做很多事儿,小到对任意 window 或者 element做小幅调整,大到使用诸如 WebGL 和 Web Audio 的 API 来生成复杂的图形和音效。
总结: 就是浏览器提供一些接口,让JavaScript可以调用,这样就可以把任务甩给浏览器了,这样就可以实现异步了!
任务队列(Task Queue)
"任务队列"是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空,“任务队列"上第一位的事件就自动进入主线程。但是,如果存在"定时器”,主线程首先要检查一下执行时间,某些事件只有到了规定的时间,才能返回主线程。
同步任务和异步任务
Javascript单线程任务被分为同步任务和异步任务.
- 同步任务会在调用栈 中按照顺序等待主线程依次执行.
- 异步任务会甩给在WebAPIs处理,处理完后有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
宏任务(MacroTask)和 微任务(MicroTask)
在JavaScript
中,任务被分为两种,一种宏任务(MacroTask
)也叫Task
,一种叫微任务(MicroTask
)。
宏任务(MacroTask)
script(整体代码)
、setTimeout
、setInterval
、setImmediate
(浏览器暂时不支持,只有IE10支持,具体可见MDN
)、I/O
、UI Rendering
。
微任务(MicroTask)
Process.nextTick(Node独有)
、Promise
、Object.observe(废弃)
、MutationObserver
(具体使用方式查看这里)
Event loop 执行过程
首先宏观上是按照这样的顺序执行.也就是前面在"Event loop的初步理解"里讲到的过程
注意:
- 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。
- 在上图的Event Table里存放着宏任务与微任务,所以在它里面 还发生了一些更细致的事情.
前面介绍宏任务的时候,提过script也属于其中.那么一段代码块就是一个宏任务。故所有一般执行代码块的时候,先执行的是宏任务script,也就是程序执行进入主线程了,主线程再会根据不同的代码再分微任务和宏任务等待主线程执行完成后,不停地循环执行。
主线程(宏任务) => 微任务 => 宏任务 => 主线程
事件循环的顺序是从script开始第一次循环,随后全局上下文进入函数调用栈,碰到macro-task就将其交给处理它的模块处理完之后将回调函数放进macro-task的队列之中,碰到micro-task也是将其回调函数放进micro-task的队列之中。直到函数调用栈清空只剩全局执行上下文,然后开始执行所有的micro-task。当所有可执行的micro-task执行完毕之后。 接着浏览器会执行下必要的渲染 UI,然后循环再次执行macro-task中的一个任务队列,执行完之后再执行所有的micro-task,就这样一直循环。
注意: 通过上述的 Event loop 顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作 DOM 的话,为了更快的 界面响应,我们可以把操作 DOM 放入微任务中。
例子分析
js复制代码console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
这里需要先理解async/await
。
async/await
在底层转换成了 promise
和 then
回调函数。 也就是说,这是 promise
的语法糖。
每次我们使用 await
, 解释器都创建一个 promise
对象,然后把剩下的 async
函数中的操作放到 then
回调函数中。
async/await
的实现,离不开 Promise
。从字面意思来理解,async
是“异步”的简写,而 await
是 async wait
的简写可以认为是等待异步方法执行完成。
关于73以下版本和73版本的区别
- 在73版本以下,先执行
promise1
和promise2
,再执行async1
。 - 在73版本,先执行
async1
再执行promise1
和promise2
。
主要原因是因为在谷歌(金丝雀)73版本中更改了规范,如下图所示:
- 区别在于
RESOLVE(thenable)
和之间的区别Promise.resolve(thenable)
。
在73以下版本中
- 首先,传递给
await
的值被包裹在一个Promise
中。然后,处理程序附加到这个包装的Promise
,以便在Promise
变为fulfilled
后恢复该函数,并且暂停执行异步函数,一旦promise
变为fulfilled
,恢复异步函数的执行。 - 每个
await
引擎必须创建两个额外的 Promise(即使右侧已经是一个Promise
)并且它需要至少三个microtask
队列ticks
(tick
为系统的相对时间单位,也被称为系统的时基,来源于定时器的周期性中断(输出脉冲),一次中断表示一个tick
,也被称做一个“时钟滴答”、时标。)。
引用贺老师知乎上的一个例子
js复制代码async function f() {
await p
console.log('ok')
}
简化理解为:
js复制代码
function f() {
return RESOLVE(p).then(() => {
console.log('ok')
})
}
- 如果
RESOLVE(p)
对于p
为promise
直接返回p
的话,那么p
的then
方法就会被马上调用,其回调就立即进入job
队列。 - 而如果
RESOLVE(p)
严格按照标准,应该是产生一个新的promise
,尽管该promise
确定会resolve
为p
,但这个过程本身是异步的,也就是现在进入job
队列的是新promise
的resolve
过程,所以该promise
的then
不会被立即调用,而要等到当前job
队列执行到前述resolve
过程才会被调用,然后其回调(也就是继续await
之后的语句)才加入job
队列,所以时序上就晚了。
谷歌(金丝雀)73版本中
- 使用对
PromiseResolve
的调用来更改await
的语义,以减少在公共awaitPromise
情况下的转换次数。 - 如果传递给
await
的值已经是一个Promise
,那么这种优化避免了再次创建Promise
包装器,在这种情况下,我们从最少三个microtick
到只有一个microtick
。
详细过程:
73以下版本
- 首先,打印
script start
,调用async1()
时,返回一个Promise
,所以打印出来async2 end
。 - 每个
await
,会新产生一个promise
,但这个过程本身是异步的,所以该await
后面不会立即调用。 - 继续执行同步代码,打印
Promise
和script end
,将then
函数放入微任务队列中等待执行。 - 同步执行完成之后,检查微任务队列是否为
null
,然后按照先入先出规则,依次执行。 - 然后先执行打印
promise1
,此时then
的回调函数返回undefinde
,此时又有then
的链式调用,又放入微任务队列中,再次打印promise2
。 - 再回到
await
的位置执行返回的Promise
的resolve
函数,这又会把resolve
丢到微任务队列中,打印async1 end
。 - 当微任务队列为空时,执行宏任务,打印
setTimeout
。
谷歌(金丝雀73版本)
- 如果传递给
await
的值已经是一个Promise
,那么这种优化避免了再次创建Promise
包装器,在这种情况下,我们从最少三个microtick
到只有一个microtick
。 - 引擎不再需要为
await
创造throwaway Promise
- 在绝大部分时间。 - 现在
promise
指向了同一个Promise
,所以这个步骤什么也不需要做。然后引擎继续像以前一样,创建throwaway Promise
,安排PromiseReactionJob
在microtask
队列的下一个tick
上恢复异步函数,暂停执行该函数,然后返回给调用者。
具体详情查看(这里)。
Node.js的Event Loop
Node.js的运行机制如下。
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
Node 的 Event loop 分为 6 个阶段,它们会按照顺序反复运行
sql复制代码┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
Node
的Event loop
一共分为6个阶段,每个细节具体如下:
timers
: 执行setTimeout
和setInterval
中到期的callback
。pending callback
: 上一轮循环中少数的callback
会放在这一阶段执行。idle, prepare
: 仅在内部使用。poll
: 最重要的阶段,执行pending callback
,在适当的情况下回阻塞在这个阶段。check
: 执行setImmediate
(setImmediate()
是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate
指定的回调函数)的callback
。close callbacks
: 执行close
事件的callback
,例如socket.on('close'[,fn])
或者http.server.on('close, fn)
。
关于Node.js的Event Loop更详细的过程可以参考这篇文章
补充: 线程和进程
在概念上
进程是应用程序的执行实例,每一个进程都是由私有的虚拟地址空间、代码、数据和其它系统资源所组成;进程在运行过程中能够申请创建和使用系统资源(如独立的内存区域等),这些资源也会随着进程的终止而被销毁。
而线程则是进程内的一个独立执行单元,在不同的线程之间是可以共享进程资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制。在系统创建进程之后就开始启动执行进程的主线程,而进程的生命周期和这个主线程的生命周期一致,主线程的退出也就意味着进程的终止和销毁。主线程是由系统进程所创建的,同时用户也可以自主创建其它线程,这一系列的线程都会并发地运行于同一个进程中。
比喻理解
一个进程好比是一个工厂,每个工厂有它的独立资源(类比到计算机上就是系统分配的一块独立内存),而且每个工厂之间是相互独立、无法进行通信。
每个工厂都有若干个工人(一个工人即是一个线程,一个进程由一个或多个线程组成),多个工人可以协作完成任务(即多个线程在进程中协作完成任务),当然每个工人可以共享此工厂的空间和资源(即同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等))。
到此你应该能初步理解了进程和线程之间的关系,这将有助于我们理解浏览器为什么是多进程的,而JavaScript是单线程。
浏览器是多进程的(一个窗口就是一个进程),每个进程包含多个线程.但JavaScript是单线程的.一个主线程(一个stack),多个子线程.
为什么JavaScript是单线程?
假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准? 所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
浏览器中其他的线程:
js既然是单线程,那么肯定是排队执行代码,那么怎么去排这个队,就是Event Loop。虽然JS是单线程,但浏览器不是单线程。浏览器中分为以下几个线程:
- js线程
- UI线程
- 事件线程(onclick,onchange,…)
- 定时器线程(setTimeout, setInterval)
- 异步http线程(ajax)
其中JS线程和UI线程相互互斥,也就是说,当UI线程在渲染的时候,JS线程会挂起,等待UI线程完成,再执行JS线程
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
题外话
网络安全行业特点
1、就业薪资非常高,涨薪快 2022年猎聘网发布网络安全行业就业薪资行业最高人均33.77万!
2、人才缺口大,就业机会多
2019年9月18日《中华人民共和国中央人民政府》官方网站发表:我国网络空间安全人才 需求140万人,而全国各大学校每年培养的人员不到1.5W人。猎聘网《2021年上半年网络安全报告》预测2027年网安人才需求300W,现在从事网络安全行业的从业人员只有10W人。
行业发展空间大,岗位非常多
网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…
职业增值潜力大
网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。
随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。
从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。
关于网络安全学习指南
学习网络安全技术的方法无非三种:
第一种是报网络安全专业,现在叫网络空间安全专业,主要专业课程:程序设计、计算机组成原理原理、数据结构、操作系统原理、数据库系统、 计算机网络、人工智能、自然语言处理、社会计算、网络安全法律法规、网络安全、内容安全、数字取证、机器学习,多媒体技术,信息检索、舆情分析等。
第二种是自学,就是在网上找资源、找教程,或者是想办法认识一-些大佬,抱紧大腿,不过这种方法很耗时间,而且学习没有规划,可能很长一段时间感觉自己没有进步,容易劝退。
第三种就是去找培训。
接下来,我会教你零基础入门快速入门上手网络安全。
网络安全入门到底是先学编程还是先学计算机基础?这是一个争议比较大的问题,有的人会建议先学编程,而有的人会建议先学计算机基础,其实这都是要学的。而且这些对学习网络安全来说非常重要。但是对于完全零基础的人来说又或者急于转行的人来说,学习编程或者计算机基础对他们来说都有一定的难度,并且花费时间太长。
第一阶段:基础准备 4周~6周
这个阶段是所有准备进入安全行业必学的部分,俗话说:基础不劳,地动山摇
第二阶段:web渗透
学习基础 时间:1周 ~ 2周:
① 了解基本概念:(SQL注入、XSS、上传、CSRF、一句话木马、等)为之后的WEB渗透测试打下基础。
② 查看一些论坛的一些Web渗透,学一学案例的思路,每一个站点都不一样,所以思路是主要的。
③ 学会提问的艺术,如果遇到不懂得要善于提问。
配置渗透环境 时间:3周 ~ 4周:
① 了解渗透测试常用的工具,例如(AWVS、SQLMAP、NMAP、BURP、中国菜刀等)。
② 下载这些工具无后门版本并且安装到计算机上。
③ 了解这些工具的使用场景,懂得基本的使用,推荐在Google上查找。
渗透实战操作 时间:约6周:
① 在网上搜索渗透实战案例,深入了解SQL注入、文件上传、解析漏洞等在实战中的使用。
② 自己搭建漏洞环境测试,推荐DWVA,SQLi-labs,Upload-labs,bWAPP。
③ 懂得渗透测试的阶段,每一个阶段需要做那些动作:例如PTES渗透测试执行标准。
④ 深入研究手工SQL注入,寻找绕过waf的方法,制作自己的脚本。
⑤ 研究文件上传的原理,如何进行截断、双重后缀欺骗(IIS、PHP)、解析漏洞利用(IIS、Nignix、Apache)等,参照:上传攻击框架。
⑥ 了解XSS形成原理和种类,在DWVA中进行实践,使用一个含有XSS漏洞的cms,安装安全狗等进行测试。
⑦ 了解一句话木马,并尝试编写过狗一句话。
⑧ 研究在Windows和Linux下的提升权限,Google关键词:提权
以上就是入门阶段
第三阶段:进阶
已经入门并且找到工作之后又该怎么进阶?详情看下图
给新手小白的入门建议:
新手入门学习最好还是从视频入手进行学习,视频的浅显易懂相比起晦涩的文字而言更容易吸收,这里我给大家准备了一套网络安全从入门到精通的视频学习资料包免费领取哦!
如果你对网络安全入门感兴趣,那么你需要的话可以在下方扫码领取!!