【libuv高效编程】libuv学习超详细教程5——prepare handle解读

libuv系列文章

prepare handle

prepare handle可以被译为准备句柄,如果程序中启动了prepare handle后,那么它在每次事件循环的时候都会被执行一遍,并且在I/O轮询之前被执行,注意,虽然它的原理与idle handle差不多,但是还是有一些差别的。

回顾上一篇文章,idle句柄在每次循环迭代中运行一次给定的回调,而且执行顺序是在prepare handle之前。它与prepare句柄的显著区别在于:当存在活动的空闲句柄时,循环将执行零超时轮询,而不是阻塞I/O,idle句柄的回调一般用来执行一些低优先级的任务,如果没有idle handle,那么事件循环将进入I/O的阻塞中。

我为什么在上一节不讲清楚这个原理呢,原因有两点,一开始我没有注意到,在写这篇文章的时候我注意到了源码,第二点原因是我想放在这里形成对比,产生的结果应该更明显,能让大家区分idle与prepare handle,其实在计算阻塞时间的函数uv_backend_timeout()中,我们可以看到一句话

  if (!QUEUE_EMPTY(&loop->idle_handles))
    return 0;

这意味着当还存在idle handle处于活跃状态时,事件循环将不会进入阻塞状态的,或者说循环将执行零超时轮询

int uv_backend_timeout(const uv_loop_t* loop) {
  if (loop->stop_flag != 0)
    return 0;

  if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop))
    return 0;

  if (!QUEUE_EMPTY(&loop->idle_handles))
    return 0;

  if (loop->closing_handles)
    return 0;

  return uv__next_timeout(loop);
}

回顾一下libuv的事件循环过程,它有一个uv__run_prepare()函数会被执行,就是在事件循环迭代的过程中处理prepare handle。

总之你可以理解为:idle与prepare handle几乎是一样的东西,只不过产生他们的handle处于活跃状态的时候对事件循环会产生不同的影响,仅此而已。

数据类型

uv_prepare_t 是prepare handle的数据类型,通过它可以定义一个 prepare handle 的实例。

typedef struct uv_prepare_s uv_prepare_t;

prepare handle的回调函数

typedef void (*uv_prepare_cb)(uv_prepare_t* handle);

如果 prepare handle 的实例想要执行回调函数,则需要传递一个uv_prepare_cb类型的回调函数到uv_prepare_start()函数中。

API

  • 初始化句柄。
int uv_prepare_init(uv_loop_t* loop, uv_prepare_t* prepare)
  • 以给定的回调函数开始句柄。
int uv_prepare_start(uv_prepare_t* prepare, uv_prepare_cb cb)
  • 停止句柄,回调函数将不会再被调用。
int uv_prepare_stop(uv_prepare_t* prepare)
```c

# example

说了那么多,首先方个prepare handle的例子吧,通过例子去讲解prepare handle相关的知识。

```c
#include <stdio.h>
#include <stdlib.h>
#include <uv.h>

int64_t num = 0;

void my_idle_cb(uv_idle_t* handle)
{
    num++;
    printf("idle callback\n");
    if (num >= 5) {
        printf("idle stop, num = %ld\n", num);
        uv_stop(uv_default_loop());
    }
}

void my_prep_cb(uv_prepare_t *handle) 
{
    printf("prep callback\n");
}

int main() 
{
    uv_idle_t idler;
    uv_prepare_t prep;

    uv_idle_init(uv_default_loop(), &idler);
    uv_idle_start(&idler, my_idle_cb);

    uv_prepare_init(uv_default_loop(), &prep);
    uv_prepare_start(&prep, my_prep_cb);

    uv_run(uv_default_loop(), UV_RUN_DEFAULT);

    return 0;
}

main函数的处理过程:

  • 定义idler实例。
  • 定义prep实例。
  • 初始化idler实例。
  • 初始化prep实例。
  • 启动idler实例,并传入对应的回调函数my_idler_cb
  • 启动prep实例,并传入对应的回调函数my_prep_cb
  • 启动事件循环。
  • 在结束后退出。

my_prep_cb回调函数的处理:

  • 打印相关的信息

my_idle_cb回调函数的处理:

  • 在每次调用回调函数的时候,对全局变量计数。
  • 在计数值达到5后,停止事件循环uv_stop()

uv_prepare_init()

其实你如果直接全局搜索uv_prepare_init这个函数的话,是找不到它的,因为libuv做了很骚的操作,将prepare、prepare以及check相关的函数都通过C语言的##连接符统一用宏定义了,并且在编译器预处理的时候产生对应的函数代码,具体源码如下:

src\unix\loop-watcher.c文件内容

#include "uv.h"
#include "internal.h"

#define UV_LOOP_WATCHER_DEFINE(name, type)                                    \
  int uv_##name##_init(uv_loop_t* loop, uv_##name##_t* handle) {              \
    uv__handle_init(loop, (uv_handle_t*)handle, UV_##type);                   \
    handle->name##_cb = NULL;                                                 \
    return 0;                                                                 \
  }                                                                           \
                                                                              \
  int uv_##name##_start(uv_##name##_t* handle, uv_##name##_cb cb) {           \
    if (uv__is_active(handle)) return 0;                                      \
    if (cb == NULL) return UV_EINVAL;                                         \
    QUEUE_INSERT_HEAD(&handle->loop->name##_handles, &handle->queue);         \
    handle->name##_cb = cb;                                                   \
    uv__handle_start(handle);                                                 \
    return 0;                                                                 \
  }                                                                           \
                                                                              \
  int uv_##name##_stop(uv_##name##_t* handle) {                               \
    if (!uv__is_active(handle)) return 0;                                     \
    QUEUE_REMOVE(&handle->queue);                                             \
    uv__handle_stop(handle);                                                  \
    return 0;                                                                 \
  }                                                                           \
                                                                              \
  void uv__run_##name(uv_loop_t* loop) {                                      \
    uv_##name##_t* h;                                                         \
    QUEUE queue;                                                              \
    QUEUE* q;                                                                 \
    QUEUE_MOVE(&loop->name##_handles, &queue);                                \
    while (!QUEUE_EMPTY(&queue)) {                                            \
      q = QUEUE_HEAD(&queue);                                                 \
      h = QUEUE_DATA(q, uv_##name##_t, queue);                                \
      QUEUE_REMOVE(q);                                                        \
      QUEUE_INSERT_TAIL(&loop->name##_handles, q);                            \
      h->name##_cb(h);                                                        \
    }                                                                         \
  }                                                                           \
                                                                              \
  void uv__##name##_close(uv_##name##_t* handle) {                            \
    uv_##name##_stop(handle);                                                 \
  }

UV_LOOP_WATCHER_DEFINE(prepare, PREPARE)
UV_LOOP_WATCHER_DEFINE(check, CHECK)
UV_LOOP_WATCHER_DEFINE(idle, IDLE)

它利用宏定义,在预处理阶段拓展成三个不同类型,但是处理逻辑一样的代码。有三种类型,分别是prepare,check,prepare。

如果你将代码中的##name或者name##或者##name##替换为prepare##type替换为PREPARE,就可以得到以下的代码:

  • 这就是编译器预处理生成的prepare handle相关的代码:
int uv_prepare_init(uv_loop_t* loop, uv_prepare_t* handle) {
    /* 初始化handle的类型,所属loop,设置UV_HANDLE_REF标志,并且把handle插入loop->handle_queue队列的队尾 */
    uv__handle_init(loop, (uv_handle_t*)handle, UV_IDLE);

    handle->prepare_cb = NULL;
    return 0;
}

int uv_prepare_start(uv_prepare_t* handle, uv_prepare_cb cb) { 
    /* 如果已经执行过start函数则直接返回 */
    if (uv__is_active(handle)) return 0;

    /* 回调函数不允许为空 */ 
    if (cb == NULL) return UV_EINVAL; 

    /* 把handle插入loop中prepare_handles队列,loop有prepare,prepare和check三个队列 */
    QUEUE_INSERT_HEAD(&handle->loop->prepare_handles, &handle->queue);

    /* 指定回调函数,在事件循环迭代的时候被执行 */
    handle->prepare_cb = cb; 

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

int uv_prepare_stop(uv_prepare_t* handle) { 
    /* 如果prepare handle没有被启动则直接返回 */
    if (!uv__is_active(handle)) return 0;

    /* 把handle从loop中相应的队列移除,但是还挂载到handle_queue中 */
    QUEUE_REMOVE(&handle->queue);

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

/* 在每一轮循环中执行该函数,具体见uv_run */
void uv__run_prepare(uv_loop_t* loop) { 
    uv_prepare_t* h;
    QUEUE queue;
    QUEUE* q;

    /* 把loop的prepare_handles队列中所有节点摘下来挂载到queue变量 */
    QUEUE_MOVE(&loop->prepare_handles, &queue);

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

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

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

        /* 把该节点移出当前队列 */
        QUEUE_REMOVE(q); 

        /* 重新插入loop->prepare_handles队列 */
        QUEUE_INSERT_TAIL(&loop->prepare_handles, q); 

        /* 执行对应的回调函数 */
        h->prepare_cb(h); 
    } 
}

/* 关闭这个prepare handle */
void uv__prepare_close(uv_prepare_t* handle) { 
    uv_prepare_stop(handle);
}

参考

libuv官方文档

例程代码获取

libuv-learning-code


 上一篇
【libuv高效编程】libuv学习超详细教程6——check handle解读 【libuv高效编程】libuv学习超详细教程6——check handle解读
libuv系列文章 【libuv高效编程】libuv学习超详细教程1——libuv的编译与安装 【libuv高效编程】libuv学习超详细教程2——libuv框架初窥 【libuv高效编程】libuv学习超详细教程3——libuv事件循
2020-04-20
下一篇 
【libuv高效编程】libuv学习超详细教程4——idle handle解读 【libuv高效编程】libuv学习超详细教程4——idle handle解读
libuv系列文章 【libuv高效编程】libuv学习超详细教程1——libuv的编译与安装 【libuv高效编程】libuv学习超详细教程2——libuv框架初窥 【libuv高效编程】libuv学习超详细教程3——libuv事件循
2020-04-18
  目录