单线程和非阻塞I/O操作在NodeJs中有什么区别?

5

我一直在阅读尽可能多的NodeJs代码,但是我对以下内容有些困惑:

Node单线程究竟意味着什么?非阻塞I/O又是什么意思?我可以通过生成子进程来实现第一个,通过使用async库来实现第二个。但我希望清楚地了解它的含义以及非阻塞I/O如何仍然会使你的应用程序变慢。


2
我觉得这个链接很方便: http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/ - Ed_
1
还有一个有用的链接:http://ejohn.org/blog/how-javascript-timers-work/ - Myrne Stol
1个回答

12

我会尽力解释。

单线程意味着Node.js Javascript运行时在特定时间点仅执行从所有已加载的代码中选择的一段代码。实际上,它从某个地方开始,并通过所有指令(调用堆栈)向下工作,直到完成。当它执行代码时,没有任何东西可以打断这个过程,所有的I/O都必须等待。谢天谢地,大多数调用堆栈相对较短,我们在Node.js中做的很多事情更多是“簿记”类型而不是CPU密集型。

然而,由于单线程性质,任何需要花费很长时间的指令都会成为系统响应能力的巨大问题。运行时每次只能执行一件事情,因此必须等待所有内容,直到该指令完成。如果任何“I/O”指令(比如从磁盘读取)会阻塞执行,则系统将在那个时间不必要地不可用。

但值得庆幸的是,我们有非阻塞式I/O。

与其等待文件被读取:

console.log(readFileSync(filePath))

你可以编写你的代码,使其不等待文件被读取:

readFile(filePath)

调用readFile几乎立即返回(可能在几纳秒内),因此运行时可以继续执行接下来的指令。但是,如果readFile调用在数据被读取之前返回,那么readFile调用就无法返回文件内容。这就是回调函数的作用:

readFile(filePath, function(err, contents) { console.log(contents))

仍然,readFile调用几乎立即返回。运行时可以继续执行。它将完成当前工作和所有其后的指令。传递的函数除了存储对它的引用外,没有什么其他用处。

稍后(可能是10ms、100ms或者1000ms)读取文件完成时,回调函数会被调用,并将文件完整内容作为第二个参数传递。在此之前,运行时可以完成任意数量的其他工作批次。

现在,我来回应一下关于生成子进程和Async库方面的评论。您两点都是错误的:

  • 生成子进程是让Node.js使用多个CPU内核的一种方法。单线程的Node.js没有使用多个内核的目的。但是,如果您使用的是多核计算机,则可以启动多个Node.js进程以利用所有这些内核。

  • Async库不会为您提供非阻塞I/O,Node.js已经为您提供了该功能。Node.js自己没有解决的是如何轻松处理来自多个回调的数据。Async库可以在这方面提供很大帮助。

由于我不是Node.js内部专家,欢迎指正!

相关问题:


非常好的回答,我在很多方面都错了。那么你认为如何处理CPU密集型任务呢?比如说,我有一个应用程序,需要从各个网站同步信息。同步可以作为后台进程以定期轮询的方式进行。你不建议使用衍生进程来完成吗? - Hick
当然不是。由于轮询是周期性的,并且每次轮询后需要完成的实际工作(即计算)可能很少,因此这是单个Node.js进程的绝佳用例。仅在您想要能够饱和多个核心时才使用多个进程的原因。 - Myrne Stol
我构建投票系统的想法是编写一个类似看门狗的库,运行在单独的线程上,不会干扰事件循环/用户请求,持续轮询并更新数据库。 - Hick
你可以在单个Node.js进程中轻松完成这项任务。Node.js甚至没有阻塞HTTP函数,因此您将执行的任何操作必须是非阻塞的。您将会花费更多的时间来处理异步编程风格,而不是关注Node的性能。 - Myrne Stol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接