前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >理解libuv的基本原理

理解libuv的基本原理

作者头像
theanarkh
发布于 2020-05-11 03:29:47
发布于 2020-05-11 03:29:47
1.9K00
代码可运行
举报
文章被收录于专栏:原创分享原创分享
运行总次数:0
代码可运行

libuv的实现是一个很经典生产者-消费者模型。libuv在整个生命周期中,每一次循环都执行每个阶段(phase)维护的任务队列。逐个执行节点里的回调,在回调中,不断生产新的任务,从而不断驱动libuv。今天我们分析一下libuv的整体架构,从而学会如何使用libuv。我们从libuv的一个小例子开始。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <uv.h>

int64_t counter = 0;

void wait_for_a_while(uv_idle_t* handle) {
    counter++;
    if (counter >= 10e6)
        uv_idle_stop(handle);
}

int main() {
    uv_idle_t idler;
     // 获取事件循环的核心结构体。并初始化一个idler
    uv_idle_init(uv_default_loop(), &idler);
    // 往事件循环的idle节点插入一个任务
    uv_idle_start(&idler, wait_for_a_while);
    // 启动事件循环
    uv_run(uv_default_loop(), UV_RUN_DEFAULT);
    // 销毁libuv的相关数据
    uv_loop_close(uv_default_loop());
    return 0;
}

使用libuv,我们首先需要获取libuv的核心结构体uv_loop_t。uv_loop_t是一个非常大的结构体。里面记录了libuv整个生命周期的数据。

uv_default_loop为我们提供了一个默认已经初始化了的uv_loop_t结构体。当然我们也可以自己去分配一个,自己初始化。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
uv_loop_t* uv_default_loop(void) {
  // 缓存
  if (default_loop_ptr != NULL)
    return default_loop_ptr;

  if (uv_loop_init(&default_loop_struct))
    return NULL;

  default_loop_ptr = &default_loop_struct;
  return default_loop_ptr;
}

libuv维护了一个全局的uv_loop_t结构体,使用uv_loop_init进行初始化。不打算展开uv_loop_init函数。因为他大概就是对uv_loop_t结构体各个字段进行初始化。接着我们看一下uv_idle_*系列的函数。 1 uv_idle_init

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 int uv_idle_init(uv_loop_t* loop, uv_idle_t* handle) {              

    /*
        初始化handle的类型,所属loop,打上UV_HANDLE_REF,
        并且把handle插入loop->handle_queue队列的队尾
    */
    uv__handle_init(loop, (uv_handle_t*)handle, UV_IDLE);                   
    handle->idle_cb = NULL;                                                 
    return 0;                                                                 
  } 

执行uv_idle_init函数后,libuv的内存视图如下

2 uv_idle_start

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 int uv_idle_start(uv_idle_t* handle, uv_idle_cb cb) {           

   // 如果已经执行过start函数则直接返回
    if (uv__is_active(handle)) return 0;                                      
    if (cb == NULL) return UV_EINVAL;                                         

    // 把handle插入loop中idle的队列
    QUEUE_INSERT_HEAD(&handle->loop->idle_handles, &handle->queue);         

    // 挂载回调,下一轮循环的时候被执行
    handle->idle_cb = cb;                                                   

   /*
       设置UV_HANDLE_ACTIVE标记位,并且loop中的handle数加一,
       init的时候只是把handle挂载到loop,start的时候handle才处于激活态
   */
    uv__handle_start(handle);                                                 
    return 0;                                                                 
  } 

执行完uv_idle_start的内存视图

然后执行uv_run进入libuv的事件循环。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;
  // 在uv_run之前要先提交任务到loop
  r = uv__loop_alive(loop);
  // 事件循环没有任务执行,即将退出,设置一下当前循环的时间
  if (!r)
    uv__update_time(loop);
  // 没有任务需要处理或者调用了uv_stop 
  while (r != 0 && loop->stop_flag == 0) {
    // 更新loop的time字段
    uv__update_time(loop);
    // 执行超时回调
    uv__run_timers(loop);
    // 执行pending回调,ran_pending代表pending队列是否为空,即没有节点可以执行
    ran_pending = uv__run_pending(loop);
    // 继续执行各种队列
    uv__run_idle(loop);
    uv__run_prepare(loop);

    timeout = 0;
    // 执行模式是UV_RUN_ONCE时,如果没有pending节点,才会阻塞式poll io,默认模式也是
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll io timeout是epoll_wait的超时时间
    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);
    // 还有一次执行超时回调的机会,因为poll io阶段可能是因为定时器超时返回的。
    if (mode == UV_RUN_ONCE) {
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    // 只执行一次,退出循环,UV_RUN_NOWAIT表示在poll io阶段不会阻塞并且循环只执行一次
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets gcc compile it to a conditional store. Avoids
   * dirtying a cache line.
   */
  // 是因为调用了uv_stop退出的,重置flag
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;
  // 返回是否还有活跃的任务(handle或request),业务代表可以再次执行uv_run
  return r;
}

我们看到有一个函数是uv__run_idle。这就是处理idle阶段的函数。我们看一下他的实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 // 在每一轮循环中执行该函数,执行时机见uv_run
  void uv__run_idle(uv_loop_t* loop) {                                      
    uv_idle_t* h;                                                         
    QUEUE queue;                                                              
    QUEUE* q;                                                                 

    // 把该类型对应的队列中所有节点摘下来挂载到queue变量,变量回调里不断插入新节点,导致死循环
    QUEUE_MOVE(&loop->idle_handles, &queue);                                

   // 遍历队列,执行每个节点里面的函数
    while (!QUEUE_EMPTY(&queue)) {                                            

      // 取下当前待处理的节点
      q = QUEUE_HEAD(&queue);                                                 

      // 取得该节点对应的整个结构体的基地址
      h = QUEUE_DATA(q, uv_idle_t, queue);                                

      // 把该节点移出当前队列,否则循环不会结束
      QUEUE_REMOVE(q);                                                        

     // 重新插入原来的队列
      QUEUE_INSERT_TAIL(&loop->idle_handles, q);                            

     // 执行回调函数
      h->idle_cb(h);                                                        
    }                                                                         
  }   

我们看到uv__run_idle的逻辑并不复杂。就是遍历idle_handles队列的节点,然后执行回调。在回调里我们可以插入新的节点(产生新任务)。从而不断驱动libuv的运行。我们看到uv_run退出循环的条件下面的代码为false

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
r != 0 && loop->stop_flag == 0

stop_flag由用户主动关闭libuv事件循环。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void uv_stop(uv_loop_t* loop) {
  loop->stop_flag = 1;
}

r是代表事件循环是否还存活,这个判断的标准是由uv__loop_alive提供

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static int uv__loop_alive(const uv_loop_t* loop) {
  return loop->active_handles > 0 ||
         loop->active_reqs.count > 0 ||
         loop->closing_handles != NULL;
}

这时候我们有一个actived handles。所以libuv不会退出。当我们调用uv_idle_stop函数把idle节点移出handle队列的时候,libuv就会退出。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int uv_idle_stop(uv_idle_t* handle) {                               
    if (!uv__is_active(handle)) return 0;                                     

    // 把idle节点从loop中idle队列移除,但是还挂载到handle_queue中
    QUEUE_REMOVE(&handle->queue);                                             

   // 清除active标记位并且减去loop中handle的active数
    uv__handle_stop(handle);                                                  
    return 0;                                                                 
  }     

执行uv_idle_stop后active_handles为0,这时候事件循环就结束了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
浅学Vue3
NPM的全称是Node Package Manager,是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。2020年3月17日,Github宣布收购npm,GitHub现在已经保证npm将永远免费。
QGS
2024/01/14
4020
Vue父组件向子组件传递一个动态的值,子组件如何保持实时更新实时更新?
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/129742.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/30
5.2K0
基于VUE + Echarts 实现可视化数据大屏展示效果[通俗易懂]
1、确定现场led拼接屏的宽高比,按照1920px*1080px的分辨率,F11全屏后刚好占满整屏且无滚动条;
全栈程序员站长
2022/09/06
6.2K0
基于VUE + Echarts 实现可视化数据大屏展示效果[通俗易懂]
Vue 监听器watch
watch就是监听一个值的变化(这个值可以是在data中定义的,也可以是父组件找那个传来的prop),并调用因为变化需要执行的方法
Autooooooo
2020/11/09
4750
Vue 折腾记 - (19) 基于Antd Design Vue 封装一个符合业务的树形组件
仔细翻了下对应的文档(antd vue),发现有那么一个树形控件,但是没有上面部分全局控制的功能。
CRPER
2019/05/07
1.6K0
Vue 折腾记 - (19)  基于Antd Design Vue 封装一个符合业务的树形组件
vue中父组件传值给子组件,父组件值改变,子组件不能重新渲染[通俗易懂]
这个方法感觉props’接收数据在调用方法之后,明明父组件的值已经改变了,但是父组件在调用子组件方法时,数据仍然没有 接收到,调用之后才接收到,这个方法暂且没用,应该是声明ref的时候声明的是当前组件的实例,然后调用时调用的也是值未改变时的属性。这个没什么用,可以用来调用子组件方法。
全栈程序员站长
2022/11/16
3.3K0
Vue父组件向子组件传值之props详解
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/135060.html原文链接:https://javaforall.cn
全栈程序员站长
2022/09/06
1.3K0
Vue父组件向子组件传值之props详解
Vue2到Vue3,重学这5个常用的API
距离Vue3发布已经过去一年多时间了,从Vue2到Vue3是一个不小的升级,包括周边生态等。
玄姐谈AGI
2023/01/10
9190
Vue2到Vue3,重学这5个常用的API
VUE3全家桶精讲
有 <script setup> 之前,如果要定义 props, emits 可以轻而易举地添加一个与 setup 平级的属性。
HelloWorldZ
2024/03/20
3070
VUE3全家桶精讲
2022前端经典vue面试题(持续更新中)
一个SPA应用的路由需要解决的问题是 页面跳转内容改变同时不刷新 ,同时路由还需要以插件形式存在,所以:
bb_xiaxia1998
2022/09/16
1.1K0
vue 实现父子组件传值和子父组件传值
用emit函数进行传参,emit函数用于防止子组件越权。向父组件传参的时候有两个内容,一个是父组件的方法,,这个方法用于监听子组件内容发生变化,及时能传给父组件。另外就是需要传的参数,这里的参数可以是一个,也可以是多个。
全栈程序员站长
2022/09/06
2.7K0
vue 实现父子组件传值和子父组件传值
vue组件另一种传值
如果有2个动态组件,一个是视图一个是参数配置,修改参数配置视图更新,这种情况可以使用父组件触发子组件事件,把对象数据传过去
tianyawhl
2021/05/27
4510
vue父组件向子组件传值_vue什么是父子组件
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/11/16
4320
Vue3 + TypeScript 开发实践总结
迟来的Vue3文章,其实早在今年3月份时就把Vue3过了一遍。 在去年年末又把 《 TypeScript 》 重新学了一遍,为了上Vue3 的车,更好的开车。 在上家公司4月份时,上级领导分配了一个内部的 党务系统开发 ,这个系统前端是由我一个人来开发,功能和需求也不怎么复杂的一个B 端 系统,直接上的 Vue3 + TypeScript + Element Plus 开发的,开发两周到最后的上线,期间也遇到很多小坑,很多无处可查,慢慢琢磨最后还是克服了。
程序员海军
2021/10/11
9320
Vue3 + TypeScript 开发实践总结
一、Vue2笔记--基础篇--09-监视属性
==================== 监视属性的属性方法 ======================
打不着的大喇叭
2024/03/11
1110
一、Vue2笔记--基础篇--09-监视属性
Vue父子组件传值的方法[通俗易懂]
<input type=”button” value=”点击触发” @click=”childClick”>
全栈程序员站长
2022/08/30
1.2K0
Vue父子组件传值的方法[通俗易懂]
常见经典vue面试题(面试必问)
如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
bb_xiaxia1998
2022/12/14
1K0
vue父组件操作子组件的方法_vue父组件获取子组件数据
我们经常分不清什么是父组件,什么是子组件。现在来简单总结下:我们将某段代码封装成一个组件,而这个组件又在另一个组件中引入,而引入该封装的组件的文件叫做父组件,被引入的组件叫做子组件。具体代码如下
全栈程序员站长
2022/09/19
7.4K0
vue父组件操作子组件的方法_vue父组件获取子组件数据
vue子组件向父组件传值的三种方式_vue子组件改变父组件的值
我是从 react 过来的,这种方式与 react 子组件向父组件传值(子组件调用父组件方法)非常相似
全栈程序员站长
2022/11/09
1K0
vue 调用子组件方法失败_Vue子组件调用父组件的方法及常见问题「建议收藏」
大家好,又见面了,我是你们的朋友全栈君。 1.子组件内不允许直接修改父组件传过来的参数。 错误实例: 子组件代码 直接对data参数进行修改,则会提示错误。 vue.runtime.esm.js?
全栈程序员站长
2022/11/11
2.4K0
推荐阅读
相关推荐
浅学Vue3
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档