惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
T
Threatpost
WordPress大学
WordPress大学
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
PCI Perspectives
PCI Perspectives
T
The Exploit Database - CXSecurity.com
Y
Y Combinator Blog
雷峰网
雷峰网
爱范儿
爱范儿
The Hacker News
The Hacker News
Last Week in AI
Last Week in AI
Simon Willison's Weblog
Simon Willison's Weblog
T
Tor Project blog
S
Securelist
宝玉的分享
宝玉的分享
L
LangChain Blog
O
OpenAI News
AI
AI
P
Privacy International News Feed
L
LINUX DO - 最新话题
D
DataBreaches.Net
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Attack and Defense Labs
Attack and Defense Labs
罗磊的独立博客
M
MIT News - Artificial intelligence
Security Archives - TechRepublic
Security Archives - TechRepublic
月光博客
月光博客
博客园 - 【当耐特】
T
Tailwind CSS Blog
C
Cybersecurity and Infrastructure Security Agency CISA
H
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
Jina AI
Jina AI
The Last Watchdog
The Last Watchdog
K
Kaspersky official blog
Webroot Blog
Webroot Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Blog — PlanetScale
Blog — PlanetScale
MyScale Blog
MyScale Blog
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
Recorded Future
Recorded Future
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog

Surmon.me

一人有限集团 你就是不敢 创造力是温柔的谎言 人类正在退出人类 AI 代替不了这样的你 脉冲点火背后的架构设计 基于 Cloudflare 生态的 AI Agent 实现 NodePress 支持用户登录了 从统计学习到通用智能 2025 投资报告:走慢的路 无依之地 会杀人的菩萨 无我不是共识 文化的积重与偏见 当下即安 科学的尽头是态度 无我不是 Egoless 信仰不因恐惧而存在 世间无解的矛与盾 先别急着做些什么 佛不需要你的皈依 真理的幻觉 两扇大门 造心里的浮屠 自胜者强 逻辑与智慧 真的相 快乐的秘密 只需愿意 是名体验 最深沉的梦 森林里倒下的树 现象与相 庸人自扰 React 与 Vue 的完美融合 开心就好吗 与原生家庭和解 两支毒箭 怎么自净其意 一尘不染 一片白云 让风穿膛而过 喝下去,然后闭嘴 活在当下 当纠结发生时 唯「我」独尊 一场把戏(时间) 一场把戏(二元对立) “我” 和 “我” 谁是众生? 船里没有人 玫瑰不需要说话 没有人能渡任何人 你我为轮,使之不再 当我们在谈论独立时 自我约束就是最好的自我接纳 从善与恶的表象出发 佛法不是心理学 别人眼中的你并不是你 高高山上走 祝你心想事成 祝你的噩梦早日发生 道是无情却有情 裙子只为自己而穿 不要盲目地评估自己 先别急着 “爱自己” 让生命欣然舞动 你无法复制乔布斯 符号不是目的 如果一件事发生了 每当我起心动念时 爱出者爱返 理解不是尊重的基础 在没有解决自己的问题之前 “知足常乐” 不是比较 逻辑是无法成为信仰的 买不到的自由 成为诵经者 伤口听不懂大道理 是立场让你不自由 - Unfreedom 天雨大不润无根之草 做自己的神 当心诱惑本身 牛洞冥思录 修罗启示录 让大脑自举 我更喜欢叫它无梦寺 存在主义也许不是解药 出南洋记 2022 的所有答复 没意义的表达 思考的记录 故宫暂行之幻想 极端的比较 解构的终极 舍利子是如何炼成的 心要野 佛教的偏见 文艺青年 我是动物
JavaScript 的单线程模型
Surmon · 2017-04-13 · via Surmon.me

含义

单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。

注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行,其他线程都是在后台配合。

JavaScript 之所以采用单线程,而不是多线程,跟历史有关系。JavaScript 从诞生起就是单线程,原因是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。 比如,假定 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript 就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。 所以,这个新标准并没有改变 JavaScript 单线程的本质。

单线程模型带来了一些问题,主要是新的任务被加在队列的尾部,只有前面的所有任务运行结束,才会轮到它执行。 如果有一个任务特别耗时,后面的任务都会停在那里等待,造成浏览器失去响应,又称 “假死”。为了避免 “假死”,当某个操作在一定时间后仍无法结束,浏览器就会跳出提示框,询问用户是否要强行停止脚本运行。

如果排队是因为计算量大,CPU 忙不过来,倒也算了,但是很多时候 CPU 是闲着的,因为 IO 设备(输入输出设备)很慢(比如 Ajax 操作从网络读取数据),不得不等着结果出来,再往下执行。 JavaScript 语言的设计者意识到,这时 CPU 完全可以不管 IO 设备,挂起处于等待中的任务,先运行排在后面的任务;等到 IO 设备返回了结果,再回过头,把挂起的任务继续执行下去。 这种机制就是 JavaScript 内部采用的 Event Loop 机制。

消息队列

JavaScript 运行时,除了一个运行线程,引擎还提供一个消息队列(message queue),里面是各种需要当前程序处理的消息。新的消息进入队列的时候,会自动排在队列的尾端。

运行线程只要发现消息队列不为空,就会取出排在第一位的那个消息,执行它对应的回调函数。等到执行完,再取出排在第二位的消息,不断循环,直到消息队列变空为止。

每条消息与一个回调函数相联系,也就是说,程序只要收到这条消息,就会执行对应的函数。另一方面,进入消息队列的消息,必须有对应的回调函数。否则这个消息就会遗失,不会进入消息队列。 举例来说,鼠标点击就会产生一条消息,报告 click 事件发生了。如果没有回调函数,这个消息就遗失了。如果有回调函数,这个消息进入消息队列。等到程序收到这个消息,就会执行 click 事件的回调函数。

另一种情况是 setTimeout 会在指定时间向消息队列添加一条消息。如果消息队列之中,此时没有其他消息,这条消息会立即得到处理; 否则,这条消息会不得不等到其他消息处理完,才会得到处理。因此,setTimeout 指定的执行时间,只是一个最早可能发生的时间,并不能保证一定会在那个时间发生。

一旦当前执行栈空了,消息队列就会取出排在第一位的那条消息,传入程序。程序开始执行对应的回调函数,等到执行完,再处理下一条消息。

Event Loop

所谓 Event Loop 机制,指的是一种内部循环,用来一轮又一轮地处理消息队列之中的消息,即执行对应的回调函数。

Wikipedia 的定义是:“Event Loop是一个程序结构,用于等待和发送消息和事件(a programming construct that waits for and dispatches events or messages in a program)”。可以就把 Event Loop 理解成动态更新的消息队列本身。

下面是一些常见的 JavaScript 任务。

  • 执行 JavaScript 代码
  • 对用户的输入(包含鼠标点击、键盘输入等等)做出反应
  • 处理异步的网络请求

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

同步任务指的是,在 JavaScript 执行进程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入 JavaScript 执行进程、而进入 “任务队列”(task queue)的任务,只有 “任务队列” 通知主进程,某个异步任务可以执行了,该任务(采用回调函数的形式)才会进入 JavaScript 进程执行。

以 Ajax 操作为例,它可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。 如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,该任务直接进入 “任务队列”,JavaScript 进程跳过 Ajax 操作,直接往下执行,等到 Ajax 操作有了结果,JavaScript 进程再执行对应的回调函数。

也就是说,虽然 JavaScript 只有一个进程用来执行,但是并行的还有其他进程(比如,处理定时器的进程、处理用户输入的进程、处理网络通信的进程等等)。这些进程通过向任务队列添加任务,实现与 JavaScript 进程通信。

想要理解 Event Loop,就要从程序的运行模式讲起。运行以后的程序叫做 “进程"(process),一般情况下,一个进程一次只能执行一个任务。如果有很多任务需要执行,不外乎三种解决方法。

  1. **排队。**因为一个进程一次只能执行一个任务,只好等前面的任务执行完了,再执行后面的任务。
  2. **新建进程。**使用 fork 命令,为每个任务新建一个进程。
  3. **新建线程。**因为进程太耗费资源,所以如今的程序往往允许一个进程包含多个线程,由线程去完成任务。

如果某个任务很耗时,比如涉及很多 I/O(输入/输出)操作,那么线程的运行大概是下面的样子。

synchronous mode
synchronous mode

上图的绿色部分是程序的运行时间,红色部分是等待时间。可以看到,由于 I/O 操作很慢,所以这个线程的大部分运行时间都在空等 I/O 操作的返回结果。这种运行方式称为 “同步模式”(synchronous I/O)。

如果采用多线程,同时运行多个任务,那很可能就是下面这样。

synchronous mode
synchronous mode

上图表明,多线程不仅占用多倍的系统资源,也闲置多倍的资源,这显然不合理。

asynchronous mode
asynchronous mode

上图主线程的绿色部分,还是表示运行时间,而橙色部分表示空闲时间。每当遇到 I/O 的时候,主线程就让 Event Loop 线程去通知相应的 I/O 程序,然后接着往后运行,所以不存在红色的等待时间。 等到 I/O 程序完成操作,Event Loop 线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。

可以看到,由于多出了橙色的空闲时间,所以主线程得以运行更多的任务,这就提高了效率。这种运行方式称为 “ 异步模式 ”(asynchronous I/O)。

这正是 JavaScript 语言的运行方式。单线程模型虽然对 JavaScript 构成了很大的限制,但也因此使它具备了其他语言不具备的优势。如果部署得好,JavaScript 程序是不会出现堵塞的,这就是为什么 Node.js 平台可以用很少的资源,应付大流量访问的原因。

如果有大量的异步任务(实际情况就是这样),它们会在 “消息队列” 中产生大量的消息。这些消息排成队,等候进入主线程。 本质上,“消息队列” 就是一个 “先进先出” 的数据结构。 比如,点击鼠标就产生一系列消息(各种事件),mousedown 事件排在 mouseup 事件前面,mouseup 事件又排在 click 事件的前面。

资料来源: JavaScript 单线程模型 - 阮一峰