【发布时间】:2020-07-24 13:15:29
【问题描述】:
所以我刚刚发现,就 C 库而言,libuv 是一个相当小的库(与 FFmpeg 相比)。在过去的 6 个小时里,我一直在阅读源代码,以更深入地了解事件循环。但仍然没有看到“非阻塞性”是在哪里实现的。在代码库中调用了一些事件中断信号或诸如此类的东西。
我已经使用 Node.js 超过 8 年,所以我熟悉如何使用异步非阻塞事件循环,但我从未真正研究过实现。
我的问题有两个:
- libuv 中发生的“循环”到底在哪里?
- 循环的每次迭代中有哪些关键步骤使其非阻塞和异步。
所以我们从一个 hello world 示例开始。只需要这样:
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>
int main() {
uv_loop_t *loop = malloc(sizeof(uv_loop_t));
uv_loop_init(loop); // initialize datastructures.
uv_run(loop, UV_RUN_DEFAULT); // infinite loop as long as queue is full?
uv_loop_close(loop);
free(loop);
return 0;
}
我一直在探索的关键函数是uv_run。 uv_loop_init 函数本质上是初始化数据结构,所以我不认为那里有太多的幻想。但真正的魔力似乎发生在uv_run,某处。 libuv repo 中的一组高级代码 sn-ps 是 in this gist,显示了 uv_run 函数调用的内容。
基本上它似乎归结为:
while (NOT_STOPPED) {
uv__update_time(loop)
uv__run_timers(loop)
uv__run_pending(loop)
uv__run_idle(loop)
uv__run_prepare(loop)
uv__io_poll(loop, timeout)
uv__run_check(loop)
uv__run_closing_handles(loop)
// ... cleanup
}
这些功能在要点中。
-
uv__run_timers:运行定时器回调?循环使用for (;;) {。 -
uv__run_pending:运行定期回调?使用while (!QUEUE_EMPTY(&pq)) {循环遍历队列。 -
uv__run_idle: 没有源代码 -
uv__run_prepare: 没有源代码 -
uv__io_poll: io 轮询吗? (不能完全说出这意味着什么)。有 2 个循环:while (!QUEUE_EMPTY(&loop->watcher_queue)) {和for (;;) {,
然后我们就完成了。程序存在,因为没有“工作”要做。
所以我想在所有这些挖掘之后我已经回答了我的问题的第一部分,并且循环具体在这三个函数中:
uv__run_timersuv__run_pendinguv__io_poll
但是没有用kqueue 或多线程实现任何东西,并且对文件描述符的处理相对较少,我不太关注代码。这可能也会帮助其他人学习这一点。
那么问题的第二部分是实现非阻塞性的这三个函数的关键步骤是什么?假设这是所有循环存在的地方。
不是 C 专家,for (;;) { 会“阻止”事件循环吗?或者它可以无限期地运行并且以某种方式从操作系统系统事件或类似的东西跳转到代码的其他部分?
所以uv__io_poll 在那个无限循环中调用poll(...)。我不认为是非阻塞的,对吗?这似乎就是它的主要作用。
查看kqueue.c 还有一个uv__io_poll,所以我假设poll 实现是一个后备,并且使用Mac 上的kqueue,这是非阻塞的?
是这样吗?它只是在uv__io_poll 中循环,并且每次迭代都可以添加到队列中,只要队列中有东西它就会运行?我仍然看不出它是如何非阻塞和异步的。
是否可以与此类似地概述一下它是如何异步和非阻塞的,以及查看代码的哪些部分?基本上,我想看看 libuv 中存在“空闲处理器空闲”的位置。在调用我们最初的uv_run 时,处理器在哪里空闲?如果它是免费的,它如何像事件处理程序一样被重新调用? (就像来自鼠标的浏览器事件处理程序,一个中断)。我觉得我在寻找中断但没有看到。
我问这个是因为我想在 C 中实现一个 MVP 事件循环,但只是不明白非阻塞性实际上是如何实现的。橡胶与道路的交汇处。
【问题讨论】:
标签: c asynchronous io event-loop libuv