我最近一直在研究Node.js的核心,我对Node平台的内部工作有一些疑问.据我了解,Node.js的工作原理如下:
Node有一个用Javascript编写的API,它允许程序员与文件系统和网络之类的东西进行交互.但是,所有这些功能实际上都是由C/C++代码完成的,也是Node的一部分.事情变得模糊不清.因此,Chrome V8引擎的工作基本上是"编译"(解释?)javascript到机器代码.V8是用C++编写的,而Javascript语言本身是由ECMA指定的,因此关键字和语言特征等都是由它们定义的.这引出了我的前几个问题:
Node Node Library如何与Node Bindings交互,因为Node Bindings是用C++编写的?
Chrome V8引擎如何在Node的上下文中解释Javascript?我知道它使用了一种名为JIT的技术,在类似的问题中提到过:(https://softwareengineering.stackexchange.com/questions/291230/how-does-chrome-v8-work-and-why-was-javascript- not-jit-first-first-first-pl)但这并没有解释如何在Node的上下文中解释Javascript.与Node一起提供的Chrome V8引擎与Chrome浏览器上运行的引擎完全相同,还是已经过修改以与Node配合使用?
这让我想到了下一个问题.因此Node具有事件驱动的非阻塞IO.它通过事件循环实现了这一点,虽然它通常被称为"节点事件循环",但它实际上是libuv库的一部分,libuv库是一个旨在提供异步IO的C++库.在较高的层次上,事件循环基本上是通过Callbacks访问的,Callbacks是一个原生的Javascript功能,也是选择Javascript作为Node项目语言的原因之一.下面是事件循环如何工作的说明:
这也可以通过这个简洁的小网站现场演示:http://latentflip.com/loupe/ 假设我们的Node应用程序需要调用外部API.所以我们写这个:
request(..., function eyeOfTheTiger() {
console.log("Rising up to the challenge of our rival");
});
Run Code Online (Sandbox Code Playgroud)
我们的调用request
被推送到调用堆栈,我们的回调被传递到某处,在那里它被保留直到请求操作完成.如果是,则将回调传递到回调队列.每次清除调用堆栈时,事件循环都会将回调队列顶部的项目推送到调用堆栈,然后执行该调用.此事件循环在单个线程上运行.出现问题的地方是有人写"阻塞"代码,或者代码从不离开调用堆栈并有效地绑定线程.如果总是在调用堆栈上执行代码,那么事件循环将永远不会将项目从回调队列推送到调用堆栈,并且它们永远不会被执行,从根本上冻结应用程序.这引出了我的下一个问题:
我发现这张图片是演示过程的一个例子:
这是我不确定Chrome V8引擎和libuv如何互动的地方.我倾向于相信节点绑定有助于这种交互,但我不太确定如何.在上图中,似乎NodeJS绑定仅与通过V8从Javascript编译的机器代码进行交互.如果是这样,我感到困惑的是V8引擎如何解释Javascript,以便Node Bindings可以区分回调和实际代码以立即执行.
我知道这是一系列非常深刻和复杂的问题,但我相信这将有助于为试图理解Node.js的人们解决很多困惑,并帮助程序员理解事件驱动的优缺点,非基本级别的非阻塞IO.
状态更新:刚看过Sencha会议的精彩演讲(链接在这里).所以在这次演讲中,演示者提到了V8嵌入指南(此处链接),并讨论了如何将C++函数暴露给Javascript,反之亦然.本质上它是如何工作的是C++函数可以暴露给V8并且还指定它希望这些对象如何暴露给Javascript,并且V8解释器将能够识别嵌入式C++函数并在它找到与之匹配的Javascript时执行它们你指定了.例如,您可以将变量和函数公开给V8,这些变量和函数实际上是用C++编写的.这基本上是Node.js的作用; 它能够在require
Javascript中添加函数,这些函数在调用时实际执行C++代码.这稍微清除了问题1,但它并没有准确地显示Node标准库如何与V8一起工作.关于libuv如何与其中任何一个进行交互仍然不清楚.
根据昌昌的回答更新:
我怎样才能在shell(Ubuntu)中看到这些线程?我用ps -eLf | grep节点| grep -v grep只看到两个:
root 16148 7492 16148 0 2 20:43 pts/26 00:00:00 ./bin/node /home/aaron/workspace/test.js
root 16148 7492 16149 0 2 20:43 pts/26 00:00:00./bin/node /home/aaron/workspace/test.js
在阅读了关于Node的线程性质的这个很好的答案后,我开始使用UV_THREADPOOL_SIZE
系统变量来改变线程池的大小,我发现了一些有趣的东西:
当我设置
process.env.UV_THREADPOOL_SIZE = 10;
Run Code Online (Sandbox Code Playgroud)
我的Node进程中有15个线程(我认为它应该是10 + 1主节点线程= 11).
看看我的剧本:
process.env.UV_THREADPOOL_SIZE = 10;
//init thread pool by calling `readFile` function
require('fs').readFile(__filename, 'utf8', function(err, content) {});
//make node not exiting
setInterval(function() {}, 1000);
Run Code Online (Sandbox Code Playgroud)
运行后我输入:
ps -Lef | grep test.js | grep -v grep
Run Code Online (Sandbox Code Playgroud)
并获得以下结果:
olegssh 4869 4301 4869 0 15 16:38 pts/0 00:00:00 /home/olegssh/node/bin/node test.js
olegssh 4869 4301 4870 0 15 16:38 pts/0 00:00:00 /home/olegssh/node/bin/node test.js
olegssh 4869 4301 4871 0 15 16:38 pts/0 00:00:00 …
Run Code Online (Sandbox Code Playgroud) 我创建了一个专门用于libuv运行循环的新线程.线程函数看起来像这样:
void thread_function()
{
uv_loop_t *loop = uv_loop_new();
uv_ref( loop );
uv_run( loop );
}
Run Code Online (Sandbox Code Playgroud)
ref计数器增量使线程保持活动状态并处于处理libuv事件的状态.我希望能够通过uv_unref
在主线程上执行来导致运行循环结束,从而导致线程退出.
但是,在检查uv_ref
源代码时,我没有看到任何保证在并发访问期间将同步对引用计数器变量的访问.另外,我没有看到在运行循环期间放弃对操作系统的控制的任何良率调用,这意味着程序将不能与其他进程很好地协作.
这让我相信我没有以正确的方式使用libuv.如果有人能解释我做错了什么,那就太好了!
这个错误究竟意味着什么?什么是"跨设备链接"?
在此libuv页面上提到它,但除了"不允许交叉设备链接"之外,它不提供任何详细信息.
我正在尝试在调用c ++回调时调用已注册的JS函数,但我得到了一个段错误,我认为这是一个范围问题.
Handle<Value> addEventListener( const Arguments& args ) {
HandleScope scope;
if (!args[0]->IsFunction()) {
return ThrowException(Exception::TypeError(String::New("Wrong arguments")));
}
Persistent<Function> fn = Persistent<Function>::New(Handle<Function>::Cast(args[0]));
Local<Number> num = Number::New(registerListener(&callback, &fn));
scope.Close(num);
}
Run Code Online (Sandbox Code Playgroud)
发生事件时,将调用以下方法.我假设这可能发生在V8正在执行JS的另一个线程上.
void callback(int event, void* context ) {
HandleScope scope;
Local<Value> args[] = { Local<Value>::New(Number::New(event)) };
Persistent<Function> *func = static_cast<Persistent<Function> *>(context);
(* func)->Call((* func), 1, args);
scope.Close(Undefined());
}
Run Code Online (Sandbox Code Playgroud)
这会导致分段错误:11.请注意,如果我直接使用addEventListener()对Persistent的引用来调用回调函数,它会正确执行该函数.
我假设我需要一个储物柜或隔离物?它看起来像libuv的uv_queue_work()可能能够解决这个问题,但由于我没有启动该线程,我看不出你将如何使用它.
在libuv源代码中,我找到了这段代码:
/* The if statement lets the compiler compile it to a conditional store.
* Avoids dirtying a cache line.
*/
if (loop->stop_flag != 0)
loop->stop_flag = 0;
Run Code Online (Sandbox Code Playgroud)
有人能解释一下吗?
什么是缓存行?
另外,我猜条件存储是一些汇编指令,它检查一些东西,如果成功,写一些值.对?
这种结构什么时候有意义?我想并不总是,因为否则编译器会一直使用条件存储,对吧?
我正在尝试学习libuv
api并编写以下测试:
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
void timer_cb(uv_timer_t* timer) {
int* i = timer->data;
--*i;
if(*i == 0) {
uv_timer_stop(timer);
}
printf("timer %d\n", *i);
//fflush(stdout);
}
int main() {
uv_loop_t* loop = uv_default_loop();
uv_timer_t* timer = malloc(sizeof(uv_timer_t));
uv_timer_init(loop, timer);
int i = 5;
timer->data = &i;
uv_timer_start(timer, timer_cb, 1000, 2000);
uv_run(loop, UV_RUN_DEFAULT);
printf("Now quitting.\n");
uv_close(timer, 0);
uv_loop_close(loop);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
运行它时,在程序完成运行之前不会显示任何输出,然后立即显示所有输出.如果我取消注释fflush
它按预期工作,则每2秒写一次.
有人可以向我解释一下吗?为什么stdout
如解释不换行后刷新,在这里和在其他地方?为什么我需要手动冲洗它?
我有一个使用libuv库的应用程序.它运行默认循环:
uv_run(uv_default_loop());
Run Code Online (Sandbox Code Playgroud)
如果发生故障,如何正常退出应用程序?目前我正在这样做,如下例所示:
uv_tcp_t* tcp = malloc(sizeof(uv_tcp_t));
int r = uv_tcp_init(uv_default_loop(), tcp);
if (r) {
free(tcp);
uv_loop_delete(default_loop);
exit(EXIT_FAILURE);
}
Run Code Online (Sandbox Code Playgroud)
应该uv_loop_delete
调用函数吗?它有什么作用?它是否会丢弃所有待处理的回调函数?它是否关闭所有当前打开的TCP连接?退出前我是否必须手动完成?
PS:无法添加标签'libuv'(声誉低于1500).有人可以创建并添加它吗?
libuv ×10
node.js ×5
c++ ×4
c ×3
javascript ×2
v8 ×2
boost ×1
boost-asio ×1
exit ×1
io ×1
linux ×1
nonblocking ×1
pthreads ×1
threadpool ×1