You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// ./lib/fs.jsfs.write=function(fd,buffer,offset,length,position,callback){functionwrapper(err,written){// Retain a reference to buffer so that it can't be GC'ed too soon.callback(err,written||0,buffer);}// ...constreq=newFSReqWrap();req.oncomplete=wrapper;if(isUint8Array(buffer)){// ...returnbinding.writeBuffer(fd,buffer,offset,length,position,req);//注意这里}// ...returnbinding.writeString(fd,buffer,offset,length,req);//注意这里};fs.writeSync=function(fd,buffer,offset,length,position){validateUint32(fd,'fd');constctx={};letresult;if(isUint8Array(buffer)){// ...result=binding.writeBuffer(fd,buffer,offset,length,position,undefined,ctx);//注意这里}else{// ...result=binding.writeString(fd,buffer,offset,length,undefined,ctx);//注意这里}handleErrorFromBinding(ctx);returnresult;};
昨天有个朋友问我:
我感觉透过现象来看本质是一个很好的入手方向。这篇文章就从我们熟悉的
fs.write
和fs.writeSync
入手,透过这些简单的API,来看看node究竟在里面做了什么。fs.write和fs.writeSync
相信有一些node基础的开发者或者之前读过我的文章的读者都会知道
console.log
是基于process.stdout.write
实现的,意即console.log
是异步操作(可能有人会提出疑问:既然是异步,如何保证输出是正确的?请自己移步./lib/console.js查看)。所以如果我们想要调试node源码异步回调的时候,如果使用console.log
会造成递归,这种情况下一般都会使用fs.writeSync
。在js层面的源码中,fs.write
和fs.writeSync
在调用的时候其实只差了一个参数:通过对比可以发现,在调用
writeBuffer
或者writeString
的时候,fs.write
多了一个req的参数,而fs.writeSync
拥有ctx参数。接下来我们去node_file.cc看看这两个到底区别在哪里。在这里我们以writeBuffer
为例:在这里我们可以很明显的开出来其中用一个
if...else
把同步和异步的逻辑区分开了。async直接调用uv_fs_write
而sync则调用SyncCall()
。当时我看到这里的时候还有些许错愕,因为node_file.cc
中提供了AsyncCall()
方法,单独这个API没有使用,于是我翻到了这个pr,是为了防止内存泄漏,所以才进行的单独的处理,具体信息可以去pr中了解。对比一下
uv_fs_write
和下面的SyncCall
,可以发现uv_fs_write
多出了AfterInteger
这个参数,AfterInteger
定义如下:其中
after.Processd()
定义如下:根据函数中的if判断以及
req_wrap->Resolve
很明显可以看出来这是一个回调函数,经过比对可以发现uv_fs_write
多出的AfterInteger
其实是一个函数。req_wrap
会在下面进行介绍,我们现在重点先关注一下uv_fs_write
。uv_fs_write
视线转移到libuv中的fs.c文件中,其中定义了
uv_fs_write
,这里只摘抄重点的部分进行解读:根据定义可以发现,上文中的
AfterInteger
其实是作为cb参数传入到了uv_fs_write
中。接下来注意一下POST
宏:在这里对cb进行了判断,如果无cb则直接调用
uv__fs_work
,如果有cb则会把uv__fs_work
放到thread_pool中调用以形成异步I/O。libuv通过这种方式,实现了同步和异步。异步的回调和同步的返回
同步
fs.writeSync
的返回首先我们先关注一下同步的
fs.writeSync
的返回,视线回到node_file.cc中,关于同步的返回在这里:可以很容易的看到,
fs.writeSync
的返回为写入的字节数。异步
fs.write
的回调在刚才已经介绍过
fs.write
会把AfterInteger
作为cb传入到uv_fs_write
中,在事件循环开始之后会在poll阶段执行AfterInteger
回调。而AfterIntenger
中最终执行的是:Resolve代码如下:
可以看到最终执行的是
MakeCallback
,如果读过我之前文章的读者,很容易联想到之前timers API的MakeCallback
。这两个MakeCallback
其实还是有区别的,区别就在于--FSReqWrap
继承自AsyncWrap
,而这个MakeCallback
的声明在async_wrap-inl.h:最终调用的
return MakeCallback(cb_v.As<v8::Function>(), argc, argv);
则在async_wrap.cc中:InternalMakeCallback
不知道大家还有没有印象,之前的文章中曾经介绍过,在node源码粗读(9):nextTick、timers API、MicroTasks注册到执行全阶段解读的event-loop阶段
章节,有这样一句话:没错,
AsyncWrap::MakeCallback
最终调用的还是node::InternalMakeCallback
。殊途同归,最终使得整体形成了一个闭环(ps:InternalCallbackScope::Close
会继续调用nextTick以及RunMicrotasks)。by 小菜
The text was updated successfully, but these errors were encountered: