大家理所当然的都知道node.js是一个单线程。那为什么一个单线程的效率可以这么高,同时处理数万级的并发而不会造成阻塞呢?
在官网上有这么一句话:** Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。*所以,理解了这个模型便不难回答上面的问题了。
如何理解上面这张图呢?我们从左上角看起,在Node.js Application代码中,每当遇到网络请求或者其它的异步操作时,node都会把它放到Event Queue,也就是事件队列中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
主线程执行完毕后,开始到Event Queue的开头取出第一个事件,然后通过Event Loop,也就是事件循环机制,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。。。如果事件执行中又发起了异步操作,则把它加到事件队列的末尾。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交由Event Loop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
大致来说就是上面这个意思,这个线程池是node一开始就创建好的,线程的个数也是一定的。在Linux和Unix平台下,线程池由node自定义实现,Windows下由IOCP实现。由于Windows和nix平台的差异,Node提供了libuv作为抽象封装层,使得所有平台兼容性的判断都由这一层来完成,并保证上层的Node与下层的自定义线程池及IOCP之间各自独立。
所以,我们所看到的node.js单线程只是一个js主线程,本质上的异步操作还是由线程池完成的,node将所有的阻塞操作都交给了内部的线程池去实现,本身只负责不断的往返调度,并没有进行真正的I/O操作,从而实现异步非阻塞I/O,这便是node单线程的精髓之处了。
在传统模型中,大多都使用多线程来解决并发的问题,因为I/O是阻塞的,单线程就意味着用户要等待,显然这是不合理的,所以创建多个线程来响应用户的请求。
多线程对比单线程:
- 创建线程耗费开销
- 创建的线程数是有限的,由机器内存决定,所以如果并发数非常大,需要多个机器
- 线程上下文切换非常耗费开销,而且线程之间共享某些数据,同步某些状态的处理也比较麻烦
- 多线程能充分利用多核CPU的优势,擅于计算
node中的单线程对比多线程:
- 充分利用单核CPU的优势,省掉了创建线程的开销,node单线程最大的性能优势便在于不需要线程上下文切换
- 对于多核CPU,可以通过硬件虚拟化的技术虚拟成单核,使得node也可以很好的利用多核CPU,或者通过child_process的方式。
- node中异步I/O的代码难以编写,调试和阅读,流程控制不明显,代码的健壮性值得考验。
- node单线程不擅于计算,因为会阻塞后面的I/O发起调用
总而言之,多线程与node单线程各有优缺点,在不同的场景下,选用合适的技术才是最明智的。
网友评论