nodejs 异步回调

异步非阻塞

nodejs 的软肋是CPU密集型,但它适应IO密集型服务。事件驱动,异步非阻塞。个人理解,异步与非阻塞细分下来是两个概念。异步与同步强调的当前线程(或进程)的两个代码块执行是没有拓扑顺序的的阻塞与非阻塞强调的是当前线程(或进程)的状态。在操作系统层面上来看,有五种网络模式方式可以进行I/O访问。(具体比较看这里

  • 阻塞 I/O(blocking IO)
  • 非阻塞 I/O(nonblocking IO)
  • I/O 多路复用( IO multiplexing)
  • 信号驱动 I/O( signal driven IO)
  • 异步 I/O(asynchronous IO)

异步、非阻塞两种方式都可以实现 nodejs 的 I/O并行。

非阻塞的方式,需要当前线程(或进程)不断的询问操作系统: “数据准备好了吗?”,直到系统回答ok。例如linux中,采用read 方法会不断重复检查 I/O的 状态;而select 和 poll 是利用的多路复用技术,检查的是文件描述符,在描述fd集合的方式上有所不同;epoll是对poll的改进。

理想的异步I/O应该是应用程序发起异步调用,而不需要进行轮询,进而处理下一个任务,只需在I/O完成后通过信号或是回调将数据传递给应用程序即可。盗图如下infoq的图

参考资料

初探Node.js的异步I/O实现
Linux IO模式及 select、poll、epoll详解
libuv 和 nodejs 的关系是什么?其他语言(如:python)是如何实现异步I/O的?

libuv 介绍

nodejs底层实现事件驱动的库是libuv,盗图如下:
nodejs底层
libuv源码传送门可以先下载源码,安装感受一下

git clone git@github.com:libuv/libuv.git  

有两种方式可以使用GCC编译,autools或者GYP工具。下面在linux环境下使用autools作为实例

$ sh autogen.sh
$ ./configure
$ make
$ make check
$ make install

linux下autogen脚本里依赖automake和libtool工具。如果是ubuntu用户,所以使用之前可以提前使用以下命令,避免有库依赖问题

sudo apt-get install make automake libtool  
make check出现问题

make check中途出现如下错误,但是可以不理会。从传送门1传送门2,上可以该问题知道是网络本身问题,个人怀疑是墙的原因。
error

make install 成功

注意中间那段话
make_install

尝试写一个程序 helloworld.c
#include <stdio.h>
#include <uv.h>

int main(){  
    uv_loop_t *loop = uv_loop_new();
    printf("now quitting.\n");
    uv_run(loop, UV_RUN_DEFAULT);
    return 0;
}

编译报错

g++ helloworld.c -o helloworld  
/tmp/ccJnLb1A.o:在函数‘main’中:
helloworld.c:(.text+0x9):对‘uv_loop_new’未定义的引用  
helloworld.c:(.text+0x28):对‘uv_run’未定义的引用  
collect2: error: ld returned 1 exit status  

从错误中可以知道是库的引用问题,从make install 结果来看可以加指定参数-L库路径/usr/local/lib;指定参数-l指定库名,尽管库名是libuv,但是链接的时候要去掉前面的lib,即-luv;

gcc helloworld.c  -L/usr/local/lib/ -luv -o helloworld  

编译成功但是运行错误

./helloworld 
./helloworld: error while loading shared libraries: libuv.so.1: cannot open shared object file: No such file or directory

添加环境变量 LD_LIBRARY_PATH

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib  

ok,运行成功

补充一点:

LIBRARY_PATH和LD_LIBRARY_PATH的区别
1. Linux gcc编译链接时的动态库搜索路径(不会递归搜索)

1、gcc编译、链接命令中的-L选项;  
2、gcc的环境变量的LIBRARY_PATH(多个路径用冒号分割);  
3、gcc默认动态库目录:/lib:/usr/lib:usr/lib64:/usr/local/lib。  

2.执行二进制文件时的动态库搜索路径

1、编译目标代码时指定的动态库搜索路径:用选项-Wl,rpath和include指定的动态库的搜索路径,比如gcc -Wl,-rpath,include -L. -ldltest hello.c,在执行文件时会搜索路径`./include`;  
2、环境变量LD_LIBRARY_PATH(多个路径用冒号分割);  
3、在 /etc/ld.so.conf.d/ 目录下的配置文件指定的动态库绝对路径(通过ldconfig生效,一般是非root用户时使用);  
4、gcc默认动态库目录:/lib:/usr/lib:usr/lib64:/usr/local/lib等。  

其中可以用 查看gcc编译连接搜索的路径

ld --verbose | grep SEARCH_DIR