今天这节课我们来聊聊JavaScript单线程的设计,众所周知 JavaScript 是以单线程的方式运行的。说到线程就自然联想到进程。那什么是进程? 什么又是线程呢? 今天我们从头来跟大家讲起。

我们每个人电脑上都会装很多的软件 QQ、微信、浏览器、百度网盘、QQ音乐、360卫士、这些软件都是运行在操作系统上,而操作系统是应用和硬件设备的中间层,他的底层是和硬件设备打交道。

而操作系统还需要承担软件治理的职责:

  • 多个软件如何同时运行(多任务的需求)?
  • 多个软件如何共同使用计算机上的存储空间(内存管理、文件系统的需求)?
  • 多个软件如何共同使用同一个外部设备(设备管理的需求)?
  • 多个软件如何相互通讯,如何进行数据交换(进程间通讯、共享内存的需求)?
  • 病毒、恶意软件如何治理(安全管理的需求)?

这些问题其实核心都指向一个问题 如何让应用软件可以共同合理使用计算机的资源,不至于出现争抢的局面。计算机最基础的硬件资源就是内存。

关于更多底层操作系统的内容我们就不过多介绍了,大家只要知道 应用-> 操作系统 -> 硬件这三者的关系即可。
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,它是运行中的程序的描述。

进程

详细解释就是,启动一个应用程序的时候(假设浏览器),操作系统会为浏览器应用程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。所以说一个进程就是一个程序的运行实例。

线程

但是你在使用电脑的时候如果有多个软件在运行 比如我开个QQ 开个浏览器 在开个百度网盘,早期是通过时间片轮转调度算法的方式把应用程序放在CPU上跑的,时间片轮转调度是这样做的每个进程被分配一个时间段,称作它的时间片 换句话说就是(进程允许运行的时间)。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。 这就是为什么你能在电脑少运行多个软件的原因,但是你为什么感受不到呢? 那是因为CPU的计算能力太快了让你毫无感知。 但是这种进程切换的方式并没有极致的利用CPU的计算能力,因为一个进程切换到另一个进程是需要一定时间的他需要(保存和装入寄存器值及内存映像,更新各种表格和队列等)大家大概了解下就行,可以看到进程在切换的时候 CPU有部分时间被浪费在了管理开销上。于是就创造出了线程的概念。

一个软件(进程)的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 b,c,d等多个块组合而成。这里b,c,d的执行是共享了A进程的上下文,CPU在执行的时候仅仅切换线程的上下文,而没有进行进程切换的。进程的切换的时间开销是远远大于线程上下文时间的开销。这样就让CPU的有效使用率得到提高。

image.png
回归到我们之前讲到了浏览器进程,在里面介绍了很多线程。 每个线程有它专门负责的事务,如果我们把时间片轮转调度 算法的目标单位换算成线程的话,那线程的意义是允许应用程序并发执行多个任务的一种机制。

下面我们围绕JavaScript引擎线程来讲讲。
首先来看段代码,假设我们要计算下面这三个表达式的值,并显示出结果。

  1. A = 1+2
  2. B = 20/5
  3. C = 7*8

在编写代码的时候,我们可以把这个过程拆分为四个任务:

  • 任务 1 是计算 A=1+2;
  • 任务 2 是计算 B=20/5;
  • 任务 3 是计算 C=7*8;
  • 任务 4 是显示最后计算的结果。

正常情况下程序可以使用单线程来处理,也就是分四步按照顺序分别执行这四个任务。

如果采用多线程,会怎么样呢?我们只需分“两步走”:第一步,使用三个线程同时执行前三个任务;第二步,再执行第四个显示任务。

通过对比分析,你会发现用单线程执行需要四步,而使用多线程只需要两步。显然,在多线程操作下可以实现应用的并行处理,从而以更高的 CPU 利用率提高整个应用程序的性能和吞吐量。特别是现在很多语言都支持多核并行处理技术,然而 JavaScript 却以单线程执行,为什么呢?

JavaScript单线程设计 - 图2

这其实这与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。所以 JavaScript 从诞生开始就选择了单线程执行。

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

下节课程我们就来讲讲在单线程的设计下一直被谈论的问题异步、回调。

特别知识点:

操作系统的启动过程
操作系统是怎么获得执行权的?这是计算机主板 ROM 上的启动程序(BIOS)交给它的。
计算机加电启动后,中央处理器(CPU)会从一个固定的存储地址加载指令序列执行。通常,这个固定的存储地址指向计算机主板的 ROM 上的一段启动程序(BIOS)。这段启动程序通常包含以下这些内容。

  • 存储设备的驱动程序,用以识别常规的外置存储设备,比如硬盘、光驱、U 盘。
  • 基础外部设备的驱动程序,比如键盘、鼠标、显示器。
  • 设备和启动配置的基础管理能力。支持执行外置存储中引导区的机器代码程序。
  • 跳转到外置存储引导区的固定地址,把执行权交给该地址上的引导程序。

引导区的引导程序有长度限制(关于这一点我在上一讲已经介绍过),只能做非常少的事情。在常规情况下,它只是简单地跳转到真正的操作系统的启动程序,但有时计算机上安装了多个操作系统,此时引导程序会提供菜单让你选择要运行的操作系统。

操作系统首先要解决的是软件治理的问题,就是让多个软件可以共同合理使用计算机的资源,不至于出现争抢的局面。

大体可分为以下六个子系统:进程管理存储管理输入设备管理输出设备管理网络管理安全管理等。

操作系统其次解决的是基础编程接口问题。这些编程接口一方面简化了软件开发,另一方面提供了多软件共同运行的环境,实现了软件治理。

内存作为计算机最基础的硬件资源,有着非常特殊的位置。我们知道,CPU 可以直接访问的存储资源非常少,只有:寄存器、内存(RAM)、主板上的 ROM(只读存储器)。

事故:QQ扫描并上传用户的浏览器历史

在今年一月份的时候在V2EX论坛上,有网友反映发现腾讯消息应用 QQ 以及 QQ 办公版 TIM 会扫描用户的浏览器历史,搜索购物记录选择性上传。引起轩然大波,QQ 在登陆10分钟之后开始扫描 Appdata\Local\下的所有文件夹,对其中 User Data\Default\History 进行进一步的扫描,User Data\Default\History 是基于 Chrome/Chromium 的浏览器默认历史纪录存放位置中招的浏览器包括但不限于如Chrome、Chromium、360极速、360安全、猎豹、2345等浏览器。