JS的单线程异步是什么意思?
单线程异步
其实,我第一次接触到这个概念,是很迷惑的,所以这里就将我自解惑之后的理解说一下,有什么不合理的也请大家指正:
首先,异步是什么意思?
和同步相对,同步是顺序执行,而异步是彼此独立,在等待某个事件的过程中继续做自己的事,不要等待这一事件完成后再工作。
异步的实现方法有哪些?
- 线程是实现异步的一个方式,是让调用方法的主线程不需要同步等待另一个线程的完成,从而让主线程干其他事情。
- 或者是直接让其他进程来执行
异步编程带来的难点
- 异常处理:异步I/O包含两个阶段,提交请求和处理结果,这两个阶段中有事件循环的调度,两者彼此不关联。异步方法通常在第一个阶段提交请求后立即返回,因为异常并不一定发生在这个阶段,try/catch在此处不会发挥任何作用
- 函数嵌套过深 {{{{{{{{{{
- 多线程编程时需要面临
- 开发人员要面临跨线程通信编程
- child_process
- cluster
多线程只是异步的一种实现手段而已,并不代表,实现异步都需要多线程。但是JS本身很明显上面两种情况都不符合,那到底是怎么回事?
我理解的js的单线程异步,其实本质上还是多线程来实现
的.
JS的单线程异步的实现
其实,如果不考虑使用进程实现异步
,那么单线程和异步不能同时成为一个语言的特性
。这句话其实是对的。
js是单线程,这里的单线程仅仅只能说明JavaScript执行在单线程中,js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node的Chrome V8)是多线程的,宿主环境通过某种方式(事件驱动模型[1])使得js具备了异步的属性。
js异步单线程在浏览器的实现: 利用了浏览器线程池
js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的
js异步单线程在Node中的实现:利用系统线程池
Node采用Chrome V8引擎处理JavaScript脚本,V8最大的特点就是单线程运行,一次只能运行一个任务,代码从上到下按顺序执行
Node大量采用异步操作,即任务不是马上执行,而是插在任务队列的尾部,等到前面的任务运行完后再执行
传统的开发方式大部分都是IO阻塞的,例如读文件,都是读取完毕才能进行下一步操作,所以需要多线程来更好的利用硬件资源,所以有时候会给人带来一种错觉:线程越多越好
异步IO也叫
非阻塞IO
,就是Node的callback,不会影响下一步操作,等到文件读取完毕,回调函数自动被执行。而不是在等待。
但是:异步IO的IO操作是在什么地方执行? -- 系统线程池
- 注意:Node是单线程的,这里的单线程仅仅只是JavaScript执行在单线程中。在Node中,无论是类Unix还是Windows平台,内部完成I/O任务的有线程池,主要用来执行IO操作
- Node在Windows下使用的异步解决方案是Windows下的IOCP:调用异步方法,等待I/O完成之后的通知,执行回调,用户无需考虑轮询。但是它的内部其实仍然是线程池原理,不同之处在于这些线程池由系统内核接受管理
- Node在Unix/Linux平台下,0.9.3版本之前使用的是libeio配合libev实现的异步I/O,在0.9.3中,自行实现了线程池来完成异步I/O
由于Windows和Unix/Linux平台的差异,Node提供了libuv作为抽象封装层,保证上层的Node与下层的自定义线程池及IOCP之间各自独立
Node.js --> libuv --> Unix/Linux自定义线程池 、Windows的IOCP
node.js中的异步IO操作和异步非IO操作 API
- 异步IO API
- 操作文件
- 网络请求
- 异步非IO API (
原理同异步IO稍有不同,并没有开启新线程,js中是先把主任务(代码任务)执行完毕,然后再去执行下面两个方法中的代码
)- setTimeout
- setInterval
总结
nodejs 是单线程的,这个单线程指的就是主线程
,主线程不能异步,只能顺序执行;但是主线程可以
调用线程池来实现并发
,线程池里的任务执行完成后发送一个事件到事件队列
,事件循环
会不断检测事件队列,发现有未处理的事件就分别调用它们的 callback 函数;callback 函数是顺序执行的,如果一个 callback 函数耗时很长,会阻塞事件循环,所以耗时很长的操作比如 IO 操作应该放在线程池里面执行;
主线程自始至终都是在事件循环中,主线程中的代码都是顺序执行,但是把耗时操作放在线程池中,然后写上 callback 函数,主线程的代码会继续向下执行,而事件循环会在适时的时候调用 callback 函数。所以在后面的代码可能比在前面的先执行完。
优点:
不会程序员本身来创建、管理、销毁线程,由运行平台来处理,简化了代码
-
事件驱动模型优于传统多线程模型编程的好处:不会创建过多的线程,避免了阻塞IO带来的性能浪费。
这个对比就相当于:有100个`任务`,需要去`执行`,但是只有一台机器CPU 传统做法就是开100条`线程`来做,但是CPU本质上还是轮询处理的。所以,其他的就相当于是在阻塞中,等着,资源浪费 单线程异步,可能就是开一条或者两条,而且还是直接在系统线程中跑。在CPU处理能力固定的情况下,充分利用现有线程,而不是开启一些线程一直在阻塞中。留下回调,处理IO完成之后的操作
参考链接: https://www.cnblogs.com/woodyblog/p/6061671.html
-
事件驱动模型:我在另一篇文章事件驱动模型、Nodejs作为事件驱动模型与多线程编程模型之间的对比中讲过,这里就不再赘述了) ↩