【发布时间】:2016-01-23 22:39:43
【问题描述】:
在过去的几天里,我一直在研究以了解 Node.js 基于事件的样式如何比经典的多线程方法处理更多的并发请求。最后是关于更少的内存占用和上下文切换,因为 Node.js 只使用几个线程(V8 单线程和一堆 C++ 工作线程加上 libuv 的主线程)。
但是如何用几个线程处理大量请求,因为最后必须阻塞一些线程等待,例如数据库读取操作。 我认为这个想法是:不是同时阻塞客户端线程和数据库线程,而是只阻塞数据库线程并在客户端线程结束时提醒客户端线程。
这就是我对 Node.js 工作原理的理解。
我一直想知道是什么赋予了 Node.js 处理 HTTP 请求的能力。 根据我到目前为止所读到的内容,我知道libuv 是谁做的:
句柄代表能够执行某些操作的长寿命对象 活动时的操作。一些例子:一个准备句柄得到它的 激活时每次循环迭代都会调用一次回调,并且 一个 TCP 每次有一个服务器句柄都会调用它的连接回调 新连接。
所以,等待传入http请求的线程是执行libuv事件循环的libuv主线程。
所以当我们写
const http = require('http');
const hostname = '127.0.0.1';
const port = 1337;
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
}).listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
...我在 libuv 中放了一个回调,当请求进来时将在 V8 引擎中执行?
事件的顺序将是
- TCP 数据包到达
- 操作系统创建一个事件并发送到事件循环
- 事件循环处理事件并创建 V8 事件
如果我在处理请求的匿名函数中执行阻塞代码,我将阻塞 V8 线程。
为了避免这种情况,我需要执行将在另一个线程中执行的非阻塞代码。我想这个“另一个线程”是 libuv 的主线程
网络 I/O 总是在单个线程中执行,每个循环的线程
此线程不会阻塞,因为使用异步的操作系统系统调用。
Linux 上的 epoll,OSX 和其他 BSD 上的 kqueue,SunOS 上的事件端口 和 Windows 上的 IOCP
我还假设http.request 正在使用 libuv 来实现这一点。
类似地,如果我需要在不阻塞 V8 线程的情况下进行文件 I/O
我将使用 Node 的 FileSystem 模块。这次 libuv 主线程不能以非阻塞方式处理这个,因为操作系统不提供这个功能。
与网络 I/O 不同,没有特定于平台的文件 I/O 原语 libuv 可以依赖,所以目前的做法是运行阻塞文件 线程池中的 I/O 操作。
在这种情况下,为了不阻塞 libuv 事件循环,需要一个经典的线程池。
现在,如果我需要查询数据库,那么不阻塞 V8 线程和 libuv 线程的所有责任都在驱动程序开发人员手中。 如果驱动程序不使用 libuv,它将阻塞 V8 引擎。
相反,如果它使用 libuv 但底层数据库没有异步功能,那么它将block a worker thread。
最后,如果数据库提供异步功能,它只会阻塞数据库线程。 (在这种情况下,我可以完全避免使用 libuv,直接从 V8 线程调用驱动程序)
如果这个结论正确地描述了 libuv 和 V8 在 Node.js 中协同工作的方式,尽管以简单的方式,我看不到使用 V8 的好处,因为我们可以直接在 libuv 中完成所有工作(除非目标是为开发人员提供一种允许以更简单的方式编写基于事件的代码的语言。
【问题讨论】:
标签: node.js multithreading concurrency v8 libuv