Node.js 因为其设计而饱受批评。 与 Java, CPython等编程语言相比,Node.js 不能直接访问线程显得有些奇怪。我们怎么能并发地执行任务呢?
好吧,实际上早在 Node.js 11 之前我们就可以使用 cluster模块来并发/并行地执行代码,正如 前一篇文章所述。

但是如果我们处在一台只有一个核的服务器上呢,我们要怎么做?

在 Node.js 11 里我们有了 worker_thread 模块,它允许我们在单核上创建(spawn)多个线程。实际上在 Node.js 10 里面我们也能通过添加 —experimental-worker 标志来使用这个模块,但是在 Node.js 11 里我们终于不用再加它了!

一个简单的用例

让我们假设需要创建一个包含 100 万个用户的文件,每个用户由 first name、middle name 和 last name 组成。
我发现了下面这个了不起的 Github 项目,它能提供一个包含 first、middle 和 last names 的数组。我们将在项目中使用这些 JSON 文件:
dominictarr/random-name
让我们通过下面的文件结构创建一个新项目:
[译] 在 Nodejs 中运行多线程 - 图1
那么让我们从 main.js文件开始:
[译] 在 Nodejs 中运行多线程 - 图2
如你所见,我们使用了 fs-extra包。它和 fs很像,但是所有的函数都返回一个 promise。它解决了这种操作带来的一个大问题:内存使用。实际上,如果我们尝试使用 Node.js 打开太多文件,它将产生一个异常然后杀掉主进程,因为它无法同时处理所有打开的这些文件(并且内存不足)。在我们的 for循环中,await会在操作结束前使循环中止:通过这种方式,我们在每个迭代中就总是只会打开一个文件。
让我们看一下 utils/index.js文件:
[译] 在 Nodejs 中运行多线程 - 图3
在这里我们仅仅只是从任意数组里面随机取出一个值。这在我们需要获取一个随机 first、middle 或 last name 时非常有用。
在我的机器(2016 MacBook Pro, 2,7 GHz Intel Core i7, 16GB RAM)上执行代码,完成任务一共花费了 3 分 32 秒。让我们看看如何用 Node.js 工作线程来提高性能!

使用多线程

为了在这个简单的程序中采用多线程,我们需要对我们的代码进行一些修改。让我们从 main.js文件开始:
[译] 在 Nodejs 中运行多线程 - 图4
首先,我们需要从 worker_threads模块中引入 Worker类。它允许我们在任何需要的时候创建一个新工人(线程)。
接下来我们可以设置要创建的线程数量:在这种情况下,我决定只创建 10 个线程。
然后我们需要计算每个线程需要生成名字的数量;这很简单,我们只需要用名字的总数除以线程的数量。
对每一个线程,我们需要创建一个新的 Worker。如你所见,这段代码将被放在 worker.js文件。
我们将给新的 Worker发送一些数据来告诉它需要创建多少名字和存放在哪里(输出文件)。
我们将一直侦听错误和退出,这样我们就能知道在我们的工作线程里面发生了什么事情。
现在让我们看看 worker.js文件做了什么:
[译] 在 Nodejs 中运行多线程 - 图5
基本上它的代码和原先的 main.js文件一样。每当我们存储一个新名字时,我们会将它发送回主线程,所以它能保持对线程内部的追踪,让我们知道里面发生了什么。
结果如何?我们只用 1 分 24 秒完成了相同的操作!比单线程版本 快了 37%

其他用例

当你需要执行一个 CPU 密集型任务时,工作线程是一个了不起的解决方案。它们使文件系统相关的操作更快,并且在需要执行任何类型的并发操作时帮助很大。最棒的是,如我们前面提到的,它们在单核机器上也能工作,所以它们能在任何服务上保证更好的性能。
实际上,我已经在一个大规模的上传操作中使用了 工作线程,在那里我需要检查上百万的用户并且存储他们的数据到数据库。采用多线程的方式,操作速度比对应的单线程快了 10 倍。
我还在图片操作里使用到了 工作线程。我需要对单个图片构建三个缩略图(不同尺寸的),在这种操作里,多线程的方式同样帮我节约了时间。
如你所见,工作线程模块可以很好地帮助你提高性能,所以如果它在某种方式下帮助你了,请告诉我吧!