Node.js单线程与并发

时间:2019-07-17 07:08:24

标签: javascript node.js multithreading concurrency parallel-processing

我有这个极小的Node.js服务器:

http.createServer(function (req, res) {
    var path = url.parse(req.url).pathname;

    if (path === "/") {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.write('Hello World');
        res.end('\n');
    } else {
        if (!handler.handle(path, req, res)) {
            res.writeHead(404);
            res.write("404");
            res.end();
        }
    }
}).listen(port);

做完一些基准测试后,我发现在高并发性(> 10000个并发连接)下性能会大大下降。我开始更深入地研究Node.js并发性,搜索越多,我就越困惑...

我从http范例中创建了一个最小的示例,以尝试更好地理解事物:

function sleep(duration) {
  return new Promise(resolve => setTimeout(resolve, duration));
}

var t0 = performance.now();

async function init() {
  await Promise.all([sleep(1000), sleep(1000), sleep(1000)]);

  var t1 = performance.now();
  console.log("Execution took " + (t1 - t0) + " milliseconds.")
}

init()
// Execution took 1000.299999985145 milliseconds.

据我了解,JavaScript在单个线程上运行。话虽这么说,但我不能像这样:

             |   1 second   |
Thread #One   >>>>>>>>>>>>>>
Thread #One   >>>>>>>>>>>>>>
Thread #One   >>>>>>>>>>>>>>

...显然这没有意义。

这是threadworker相对的术语问题吗?

             |   1 second   |
Thread #One  (spawns 3 workers)
Worker #One   >>>>>>>>>>>>>>
Worker #Two   >>>>>>>>>>>>>>
Worker #Three >>>>>>>>>>>>>>

???

Node.js如何是单线程的,但能够并行处理三个函数?如果我对并行工作程序正确,http是否为每个传入的连接生成多个工作程序?

1 个答案:

答案 0 :(得分:1)

JavaScript程序中的线程通过为任务(事件)/作业队列提供服务来工作。从概念上讲,这是一个循环:从队列中拾取一个作业,将该作业运行至完成,拾取下一个作业,将该作业运行至完成。

请记住,您的诺言示例如下:

  1. 使用输入文件运行Node.js会解析代码,并将作业排队以运行脚本中的顶级代码。
  2. 主线程接该工作并运行您的顶级代码,该代码:
    1. 创建一些功能
    2. var t0 = performance.now();
    3. 呼叫sleep(1000),其中
      • 创造承诺
      • 设置计时器以在大约1000毫秒内执行回调
      • 兑现诺言
    4. 再打两次sleep(1000)。现在有三个Promise,并且三个计时器回调大致安排在同一时间。
    5. 等待Promise.all兑现这些承诺。这样可以保存异步函数的状态并返回一个Promise(无用,因为没有使用init的返回值)。
    6. 现在,运行顶级代码的工作已经完成。
  3. 线程现在处于空闲状态,正在等待作业执行(I / O完成,计时器等)。
  4. 闲置大约一秒钟后,作业将排队以调用第一个计时器回调。这是如何发生的与实现有关:
    1. 主线程可能在循环检查工作队列的过程中还检查计时器列表,以查看是否有任何计时器要运行。
    2. 计时器系统可能具有自己的非JavaScript线程,该线程执行这些检查(或从OS机制获取回调),并在需要运行计时器回调时将作业排队。
    3. 对于I / O(由于在您的示例中计时器是I / O的替身),Node.js使用一个或多个单独的线程来处理来自操作系统的I / O完成并为以下任务排队它们放在JavaScript作业队列中。
  5. JavaScript线程接收该作业以调用计时器回调。它做到了,这满足了第一个sleep承诺。
  6. 履行诺言会在“微任务”(或“诺言工作”)中排队,以调用诺言的履行处理程序(then处理程序),而不是“宏任务”(标准任务)。现代JavaScript引擎会在将它们排队的标准作业的末尾处理承诺作业(即使主队列中已经有另一个标准作业等待完成-承诺作业会跳过主队列)。所以:
    1. 完成第一个sleep承诺将使承诺工作排队。
    2. 在计时器回调的标准作业结束时,线程选择诺言作业并运行它。
    3. promise作业在Promise.all的逻辑内触发对实现处理程序的调用,该逻辑存储实现值并检查是否已等待其等待的所有诺言。在这种情况下,它们不是(两个仍然出色),因此无可奉告,诺言工作也就完成了。
  7. 线程返回主队列。
  8. 几乎立即,队列中有一个作业来调用下一个计时器回调(或者可能有两个作业来调用它们)。
    1. 该线程选择第一个线程并执行它,它满足第二个sleep承诺,对一个保证工作进行排队以运行其履行处理程序
    2. 它拿起排队的承诺工作并按照Promise.all的逻辑调用实现处理程序,该逻辑存储实现值并检查是否所有承诺都已实现。它们不是,所以它只是返回。
    3. 它拾取并执行下一个标准作业,该作业完成了第三个sleep承诺,并为其履行处理程序排队了一个承诺工作。
    4. 它拾取承诺工作,并按照Promise.all的逻辑运行实现处理程序,该处理程序存储实现结果,看到所有承诺都已兑现,然后将承诺工作排队以运行 履行处理程序。
    5. 它将拾取新的Promise作业,从而推进异步init函数的状态:
      • init函数执行var t1 = performance.now();,并显示t1t0之间的差异,大约是一秒钟。

(该代码中)只涉及一个JavaScript线程。它正在运行一个循环,为队列中的作业提供服务。计时器可能有单独的线程,或者主线程可能只是在检查作业之间的计时器列表。 (我必须深入研究Node.js代码才能知道哪个,但我怀疑后者,因为我怀疑它们使用的是操作系统特定的调度功能。)如果是I / O完成而不是计时器,那我相当确定这些是由单独的非JavaScript线程处理的,该线程会响应来自操作系统的完成并将JavaScript线程的作业排队。