我们都知道libuv是异步非阻塞的,那么它究竟是如何实现异步的呢?
Libuv中文编程指南上如是说
libuv提供的文件操作和 socket operations 并不相同. 套接字操作使用了操作系统本身提供了非阻塞操作,而文件操作内部使用了阻塞函数,但是 libuv 是在线程池中调用这些函数, 并在应用程序需要交互时通知在事件循环中注册的监视器.
参考: http://www.infoq.com/cn/articles/nodejs-asynchronous-io
也就是说libuv除了借助于操作系统自身的分阻塞操作外,还使用了阻塞 + 多线程的实现方式。
下面通过分析libuv自带的例子来分析libuv异步的实现方式。
对于套接字操作,先看看libuv中自带的用例echo-server.c,其中这样的代码:
HELPER_IMPL(udp4_echo_server) { loop = uv_default_loop(); if(udp4_echo_start(TEST_PORT)) return 1; uv_run(loop, UV_RUN_DEFAULT); return 0; }
这里uv_run里面包含了异步事件polling和处理的循环。
再看udp4_echo_start函数
static int udp4_echo_start(int port) { intr; server = (uv_handle_t*)&udpServer; serverType = UDP; r =uv_udp_init(loop, &udpServer); //这里将loop赋值给了udpServer的loop成员,然后后续会对loop进行成员变量的值进行设定。在uv_run中会根据loop获取其在udpServer中的双链表成员所在的节点,进行处理然后删除。 if(r) { fprintf(stderr, "uv_udp_init: %s\n", uv_strerror(r)); return 1; } r =uv_udp_recv_start(&udpServer, echo_alloc, on_recv); if(r) { fprintf(stderr, "uv_udp_recv_start: %s\n", uv_strerror(r)); return 1; } return 0; }
追寻代码有如下的调用过程:
uv_run->uv__run_pending-> uv__udp_io
uv__udp_io[udp.c]->uv__udp_recvmsg->会调用handle的recv_cb实际上->用户设置的callback
用户的回调是被调用了,那设置套接字非阻塞呢?
再详细看看上面的例子,找到如下的代码处理流程:
[echo-server.c]udp4_echo_start->uv_udp_recv_start->[uv-common.c]uv__udp_recv_start->[udp.c] uv__udp_maybe_deferred_bind->uv__udp_bind->uv__socket->[core.c]调用uv__nonblock设置套接字非阻塞。。。
针对文件操作,虽然使用了阻塞函数,但是将阻塞操作放到了线程池中。
看看Test-fs.c中的例子,找到如下的函数调用链:
(创建线程池中的各个线程只需初始化一次
Test-fs.c->uv_fs_open->fs.c INIT->POST ->uv__work_submit ->threadpool.c->uv__work_submit->uv_once->init_once->uv_thread_create)
Test-fs.c->uv_fs_open->fs.c INIT->POST ->uv__work_submit-> post
在post中将work_req也就是uv__work挂到threadpool.c模块的staict双向链表wq中。
后续,线程池中的线程从wq中捞出任务进行处理。这样实际上是多线程进行了处理。处理完成之后会调用uv__fs_done,在uv__fs_done中调用用户设置的回调函数。
本人享有博客文章的版权,转载请标明出处http://blog.csdn.net/baidu20008