单线程
预计阅读时间: 8 分钟我们都知道 nodejs 是使用 js 语言,然后去快速的实现一个高效可伸缩的高性能 web 服务,但是在我们的认知当中,常见的 web 服务都是由多线程或者说多进程来实现的,那单线程的操作是如何来支持高并发的呢,以及 node js 的单线程,它是否是存在着一些缺点,那下面的话我们就具体的来去说一说。
在 nodejs 的底层,通过 异步 io、 事件循环,然后再去加上事件驱动的架构,去通过回调通知的方式来实现非阻塞的调用,以做到并发,具体的表现就是程序代码当中如果存在多个请求的时候,是无需阻塞的,那他会由上向下去执行,然后去等待 libuv 库完成工作之后,再按照顺序来通知相应的事件回调去触发执行就可以了,那这样的话单线程也就完成了多线程的工作。
nodejs 的单线程,它其实指的是主线程是单线程的,而不是说 nodejs 只有单线程,或者说 nodejs 只能去做单线程,因为我们都知道 nodejs 平台下的 js 代码,它最终都是由 v8 来执行的,而在 v8 当中是只有一个主线程来执行我们的代码,所以这也就是我们平日里所说的单线程,但是在 libuv 库当中,它是存在着一个线程池的,就像我们图中的右侧所看到的部分。

默认的情况下,它的里边是会有四个线程,我们可以去将 nodejs 程序的异步请求,先分为网络 io 和非网络 io 以及 非 io 的这样一些异步操作,针对于网络 io 操作来说,libuv 库就会去调用当前平台相对应的 io 接口去进行处理,而另外的两种异步操作就会去使用线程池中的线程来完成处理,那当然如果说我们觉得这四个线程不够用的情况下,也可以去修改相应的默认配置,然后来增加默认的线程数,但是这个操作一般情况下我们是都不需要去执行的。
那说完了这些之后上述的这些内容就算是 nodejs 单线程的优点,因为我们可以看到使用单线程,它做到了之前多线程能够做到的事情,那这样一来也就提高了线程的安全,那同样也减少了线程间切换的时候,会存在的一些 cpu 开销和内存同步开销等这样的一些问题。
但是技术总归不是完美的,单线程同样也存在着它的一些劣势,例如说我们再去处理一些 cpu 密集型的任务时,那他就会去过多的占用 cpu,那这样一来后边的逻辑就必须等待了,而且单线程也无法去体现出多核 cpu 的优势,当然这些问题在 node 的后续的版本当中,也都给出了一些解决方案,比如说 cluster 集群。
接下来我们就可以先去通过一段代码,来演示一下单线程在处理 cpu 密集型问题时,所存在的一些阻塞的现象。
我们先总结一下当前的逻辑,我们用 nodejs 里面一个内置的核心模块叫 http,然后通过现在 createServer 创建了一个服务,那就相当于在我们本地开启了一个服务,如果我们现在去运行这个 js,控制台会立马打印服务启动了,那我们这个服务就相当于是有了,然后紧接着我们就可以去使用浏览器来充当客户端,然后去访问我们 localhost:8080,那这时候就会在我们网页上帮我们显示出server starting...这样的一部分内容。
现在想要演示的是单线程在遇到一些 cpu 密集型,也就是我们一些计算型的耗时的操作的时候,我们对后续代码的一个影响,所以这个时候我们就想办法在前边去加上一些延迟,让我们的代码能够去进行休眠,所以这个时候我们就可以自己来添加一个自定义函数去完成。
重新执行一下我们会发现控制台的打印的确是没有立即显示出来,然后等待了 4 秒钟后就显示打印了。
以上的这些就是关于 node js 单线程的一些特点的介绍,这里去总结一下。
第一就是 nodejs 虽然是单线程的机制,但是我们去使用它,然后再配合着异步 io 和事件循环,就可以去实现高并发的请求。
第二我们所说的 nodejs 是单线程的,它其实指的是我们运行 js 代码的主线程是单线程的,也就是说我们这个 v8 引擎里边,专门用来去执行我们的这个 js 代码的那个部分,它是单线程的,但是在 libuv 库里边,它是存在着一个存放多个线程的线程池的。
第三点就是 nodejs 的单线程,也决定了它是不太适合去处理一些 cpu 密集型任务的,比如说这个 cpu 密集型,最常见的就是计算,我们在这里边,假如说有一段代码是经常占用着我们这个 cpu 的,那这个时候你会发现后续的代码是没有办法去执行的。