疑问
最近在知乎上面看到一条有趣的关于node.js面试问题
Node 里有readFile和对应的同步方法readFileSync,但http.get() 却没有 http.getSync(),如果要实现一个http.getSync(),怎么做?
http://www.zhihu.com/question/24648388
围绕这个问题来展开我们的思考,文件读取是node.js最重要的io模块,也是最特别的模块,他的特别在于它是唯一一个拥有异步和同步,readFile
异步,readFileSync
同步,两种api的模块,相信每一个nodejser 都会觉得是不是意味着,我们能在node.js里面写出同步的处理函数?本文将会围绕这个核心问题来展开分析。
API
fs.readFile 异步读取
js基本流程
通过fs.stats 读取文件大小,当文件大小等于0的时候,可能是内核函数无法正确读取文件大小,对于这种情况,node.js 会进行分片读取,每次异步读取8k内容到buffer里,并将buffer的内容存储到数组,直到文件无法读取为止,同时会在无法读取的时候,将刚才8k们的数组,合并为一个buffer返回结果,对于能够正确读取文件大小的时候,则直接生成与文件大小相同,并将内容读取到buffer里面,当size大于4g的时候,直接报错返回。
fs.readFileSync 同步读取
主要流程与异步相同,唯一不同的地方,是readFileSync 不带回调参数,具体结果是直接返回。
底层实现
由此可见,究竟执行的方式是异步还是同步,问题的核心在与底层实现,让我们看看libuv是怎么说的。
截取libuv的原话,是怎么说的
The libuv filesystem operations are different from socket operations. Socket operations use the non-blocking operations provided by the operating system. Filesystem operations use blocking functions internally, but invoke these functions in a thread pool and notify watchers registered with the event loop when application interaction is required.
libuv的文件操作方法跟socket的实现方式原理不一样,对于socket的处理,libuv是调用系统本身的非阻塞特性, 而fs模块,则是调用通过线程池来模拟,监听线程池的任务,当任务完成后,回调信息给调用者。
而最终libuv采用异步,还是同步的方式的途径,是根据 调用的参数决定的
UV_EXTERN int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file, void* buf, size_t length, int64_t offset, uv_fs_cb cb);
当callback为空的时候,函数同步执行,当过callback存在函数的时候,异步执行。
总结
getSync 这种方法,在libuv框架下是无法实现的,libuv从底层调用开始就是非阻塞的,是操作系统提供,所以你无论用什么黑魔法实际效果都是异步,但对于fs模块来说,仅仅是利用线程池模拟出异步的效果,所以能,非系统自带的特性,所以能写出同步的api。