【libuv高效編程】libuv學習超詳細教程5——libuv prepare 準備句柄解讀

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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章