前言
在JS中"异步"是一个重要的概念,以它为原点引出了Promise期约、异步函数async/await等重要的概念,因此"异步"在我们构建前端知识体系的过程中占据了重要的地位。在实际的工作中,由于经常需要向服务端请求资源,因此我们也会经常和"异步"打交道,或是利用它来实现我们自己的目的,或是解决它所带来的各种问题。 总之"异步"对于我们来说并不陌生可以说是一位"老朋友"了,但是就在某一天,我发现我似乎并不了解它。
一、探索异步的含义
1.熟悉的陌生人
最近,我的"重学JS计划"推进到异步编程部分,异步编程首当其冲就是要去了解JS中同步与异步的概念,这是一切异步编程技术的起源。其中"同步"的概念并不难理解,大致就是代码严格按顺序执行,只有先执行完前面的代码才能执行后面的代码。但是"异步"的概念就让我犯了难,书中对于异步的解释如下:
异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。
这段解释让我摸不着头脑,其中每一个字我都认识,但合在一起就不知道是什么意思了。此时我开始问自己一个问题:"异步究竟是什么?" 我发现自己无法回答这样一个问题,我无数次的接触过异步,但是我却无法用语言给它下一个准确的定义。于是此刻我便开始查阅资料,尝试来解答这个问题。
2.其他人对于异步的解释
想要了解异步的概念,我首先想到的便是看看其他人对于异步的解释。下面我精选了两篇博客中对"异步"的解释:
- 每一个任务有一个或多个回调函数,前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务·结束就执行;所以程序的执行顺序与任务的排列顺序是不一致的;
- 另一种简单的解释:一个任务分两段,先执行第一段,再执行其他任务;当第一段有执行结果时,回去执行第二段
异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有等主线程任务执行完毕,"任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。
我的评价:
第一篇博客中给出的这两种解释,简单明了,它们的大概思路是相似的,都是通过阐述任务与回调的联系和执行顺序,进而解释了"异步"的概念。
第二篇博客则是从JS运行机制的角度,介绍了"异步"的概念。
这两篇博客中的解释给了我一定的启发,但是我感觉这些解释都还不够,他们都在"取巧",说白了是通过现象去解释原理。看完这些之后我们还是没办法理解之前书中对于"异步"的解释。
3.计算机科学中的异步
很快我注意到了书中的这样一段话:
同步行为和异步行为的对立统一是计算机科学的一个基本概念。
这段话启发了我,或许我应该跳出JS前端的范畴,从更高维度计算机科学的角度,来理解"异步"的概念。
于是我便开始从这个角度来搜索有关"异步"的资料,在这一过程中百度百科给了我很大的帮助。
我在百科中发现了一些有价值的信息:
与同步相对应,异步指的是让CPU暂时搁置当前请求的响应 , 处理下一个请求,当通过轮询或其他方式得到回调通知后, 开始运行。
异步无需占用额外的线程。
异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。
从上述的信息中我主要提炼出了两个重要的信息
- 这里其实已经给异步的含义做了一个很好的概括,即:搁置当前的请求,处理下一个请求,之后在得到"通知"后会重新运行
- 异步似乎与线程有着紧密的联系,这里还特别将异步与多线程进行了比较
似乎到了这里之前的问题已经有了答案,不过现在我有了新的问题,什么是线程?什么又是多线程?
4.线程
为了搞清楚线程的含义,我再次"问卜"于百科,百科中对线程的定义如下:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
基于这个概念和我看到的其他的一些资料,我陈述一下我的理解:
- 线程脱胎于进程
- 一个进程中可以分割多个线程,这些线程可以同时执行不同的任务
- 我们的程序应该就是通过线程来执行的
- 线程是单一顺序的,意思就是线程实际上是在以同步的方式执行任务
之后在研究多线程的概念时,我又发现了一个非常有价值的信息:
这里就将所有问题都回答的很清除了:
系统通过线程来执行代码,但线程是顺序执行的(即同步),因此在执行"高消耗"任务时会出现阻塞问题。多线程可以解决阻塞问题,它将原本在一个线程上顺序执行的任务拆分开,放到多个线程中同步执行。
5.重看异步的概念
在绕了这样一大圈,在了解了各种概念后,我们再回来看看最开始书中对于异步的解释:
异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。
前半句的关键点肯定就是在 "系统中断" 上,我找到了一个对于系统中断较为恰当的解释:
所谓中断是指CPU对系统发生的某个事件做出的一种反应,CPU暂停正在执行的程序,保留现场后自动地转去执行相应的处理程序,处理完该事件后再返回断点继续执行被"打断"的程序。
因此前半句的想表达的含义就是:异步行为也会暂停正在执行的任务,转去执行其他任务,之后会返回中断的地方继续执行。
而后半句中难以理解的就是什么是"进程外部的实体"?
我的理解是,每个程序都对应一个单独的进程,因此"进程外部实体"也就是当前程序之外的实体。那么例如我们通过http请求一个资源时,服务器就可以说是"进程外部实体"。所以"当前进程外部的实体可以触发代码执行"就可以理解为,服务器返回资源是可以触发程序中的代码执行的,其实也就对应了"回中断的地方继续执行"。
最后我再对书中的异步概念做一个完整的翻译:
异步行为是浏览器线程执行代码的一种方式,它与系统中断相似,会暂停正在执行的任务,转去执行后面的任务,之后会返回中断的地方继续执行,这个重新执行的的时机与当前程序之外的某些实体相关。
二、相关概念的梳理与总结
1.进程与线程
我在博客《线程与进程,你真得理解了吗_进程和线程的区别-CSDN博客》找到了这张图片,他非常传神的表达了CPU、进程、线程三者之间的关系的关系。
(1) 什么是进程?
我简单的理解就是:进程就是一个程序执行的过程。进程是操作系统进行资源分配和调度的一个独立单位。
(2) 进程与线程的关系
进程衍生出线程,一个进程中可以包含多个线程,这些线程共享进程的资源。
(3) 进程与线程的区别
- 进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
- 线程是进程的一部分 , 一个进程中至少有一个线程
- 进程有独立的地址空间,所以进程间的切换开销大;而同一个进程中的线程共享进程的地址空间,所以线程间的切换开销小
2.线程与多线程
(1) 什么是线程?
线程是系统对代码的执行过程,如果将系统当做一个员工,被安排执行某个任务的时候,他不会对任何其他的任务作出响应。只有当这个任务执行完毕,才可以重新给他分配任务。
(2) 线程阻塞问题
如果让主线程去执行一个消耗大(耗时长)的任务,它会等到这个任务执行完成,才会继续执行后面的代码。在这段时间之内,主线程一直处于"忙碌"状态,也就是无法执行任何其他功能。体现在界面上就是,用户的界面完全"卡死"。这就是线程阻塞。显然这样会带来糟糕的用户体验。
(3) 多线程
多线程是指,将原本线性执行的任务分开成若干个子任务同步执行,这样做的优点是防止线程"堵塞",增强用户体验和程序的效率。
(4) 多线程原理
多线程实际上是把一个进程划分为多个线程,每个线程轮流占用CPU的运算时间,操作系统不断地把线程挂起、唤醒、再挂起、再唤醒,如此反复,由于现在CPU的速度比较快,给人的感觉是多个线程在同时执行,就好像有多个CPU存在于计算机中一样。
多线程的一个典型例子是:用资源管理器复制文件时,一方面在进行磁盘读写操作,同时一张纸不停地从一个文件夹飘到另一个文件夹,这个飘的动作实际上是一段视频剪辑,也就是说,资源管理器能够同时进行磁盘读写和播放视频剪辑。
3.异步与多线程
(1) 什么是异步?
异步是指线程暂停执行当前的任务,先执行后面的任务,等到"合适的时机"再回来继续执行当前任务。
(2) 异步与多线程的异同
"异步",从字面来讲,好像是在不同的(异)的ways上do something,那首先想到的词可能是"一边...一边...",比如'小明一边吃雪糕一边写作业',这完全没毛病,雪糕吃完了,作业也写完了,这就是异步?那就大错特错了!
上面的这段话是我从某个文章中摘录的,为什么"一边吃雪糕一边写作业"不是异步呢?
因为这种"并行"的代码执行方式实际上是多线程。
异步与多线程的相同点在于,它们都可以达到避免线程阻塞的目的。
异步与多线程的区别在于,异步操作只使用一个线程,无需额外的线程负担,而多线程自然需调用多个线程。
4.JS中的同步与异步
(1) JS的单线程特性
在谈论JS中的同步与异步之前,我们必需要了解一个重要的前题,那就是:JS是一个单线程的语言。
也就是说在JS中只能使用一个线程,是无法实现"多线程"的。
(2) JS单线程设计的原因
JS作为脚本语言,主要的作用是与用户互动、操作DOM,这决定了JS只能是单线程的;否则会带来很复杂的同步问题。
例如:一个线程在某个DOM上添加内容,而另一个线程删除这个DOM,那么浏览器要如何反应呢?这就乱套了。
(3) JS中同步异步的概念总结
之前我们谈到,线程执行任务是严格按照顺序进行的,只有执行完了前面的任务才可以执行后面的任务,这种线程默认的执行方式就是同步行为。
当我们用线程去执行一个高延迟的任务时,就会存在线程阻塞问题 。可以通过多线程 或异步 的方式解决线程阻塞问题。但是由于JS是一个单线程语言,所以它就只能够使用异步的方式解决线程阻塞问题。
总结:
- JS同步行为: 系统严格按照出现顺序执行代码,必需等待前一个任务执行完毕才能执行下一个任务。
- JS异步行为:系统中断当前代码的执行,等到"合适的时机"再去重新执行中断的任务,可以中断前一个任务直接执行下一个任务。
(4) JS中同步与异步的对立统一
JS中的同步与异步其实是一个对立统一的关系,它们之间存在着一个核心的矛盾------关于代码阻塞的矛盾。
在一般情况下我们不希望程序一直等待某个高延迟的任务,这样会造成页面的卡顿,影响用户的体验。但在其他的一些时候,由于后续代码依赖高延迟的资源,又必需去进行等待。因此理想的效果是我需要阻塞时就可以阻塞,我不需要阻塞时就不能阻塞。
JS中所有与同步异步相关的知识和技术其实目的都是为了解决这个矛盾,从而达到理想的效果。JS的这一套单线程事件循环模型,保证了程序不会受到代码阻塞的影响;而回调等一系列异步编程方案,则保证依赖延迟资源的代码可以在获取到资源后执行。
参考资料