libuv 采用了 異步 (asynchronous), 事件驅動 (event-driven)的程式設計風格, 其主要任務是為開人員提供了一套事件循環和基于I/O(或其他活動)通知的回調函數, libuv 提供了一套核心的工具集, 例如定時器, 非阻塞網絡程式設計的支援, 異步通路檔案系統, 子程序以及其他功能.
事件循環(Event loops)
在事件程式設計模型中, 應用程式通常會關注某些特定的事件, 并在事件發生後對其作出響應. 而收集事件或監控其他事件源則是 libuv 的職責, 程式設計人員隻需要對感興趣的事件注冊回調函數, 在事件發生後 libuv 将會調用相應的回調函數. 隻要程式不退出(被系統管理人員 kill 掉), 事件循環通常會一直運作, 下面是事件驅動程式設計模型的僞代碼:
while there are still events to process:
e = get the next event
if there is a callback associated with e:
call the callback
适用于事件驅動程式設計模型的例子如下:
- 檔案已經準備好可寫入資料.
- 某一 socket 上存在資料可讀.
- 定時器已逾時.
事件循環由 uv_run
計算機程式最基本的活動是輸入輸出的處理, 而不是大量的數值計算, 而使用傳統輸入輸出函數(read, fprintf 等)的問題是它們都是 阻塞 的. 将資料寫入磁盤或者從網絡讀取資料都會消耗大量時間, 而阻塞函數直到任務完成後才傳回, 在此期間你的程式什麼也沒有做, 浪費了大量的 CPU 時間. 對于追求高性能的程式而言, 在其他活動或者 I/O 操作在進行盡量讓 CPU 不被阻塞.
标準的解決方案是使用線程, 每個阻塞的 I/O 操作都在一個單獨的線程(或線程池)中啟動, 當阻塞函數被調用時, 處理器可以排程另外一個真正需要 CPU 的線程來執行任務.
Libuv 采用另外一種方式處理阻塞任務, 即 異步 和 非阻塞 方式.大多數現代作業系統都提供了事件通知功能, 例如, 調用 read 讀取網絡套接字時程式會阻塞, 直到發送者最終發送了資料(read 才傳回). 但是, 應用程式可以要求作業系統監控套接字, 并在套接字上注冊事件通知. 應用程式可以在适當的時候檢視它所監視的事件并擷取資料(若有). 整個過程是 異步 的, 因為程式在某一時刻關注了它感興趣的事件, 并在另一個時刻擷取(使用)資料, 這也是 非阻塞 的, 因為該程序還可以處理另外的任務. Libuv 的事件循環方式很好地與該模型比對, 因為作業系統事件可以視為另外一種 libuv 事件. 非阻塞方式可以保證在其他事件到來時被盡快處理 [1].
Note
I/O 是如何在背景運作的不是我們所關心的, 但是由于我們計算機硬體的工作方式, 線程是處理器最基本的執行單元, thread as the basic unit of the , libuv 和作業系統通常會運作背景/工作者線程, 或者采用非阻塞方式來輪流執行任務.
Hello World
具備了上面最基本的知識後, 我們就來編寫一個簡單 libuv 的程式吧.該程式并沒有做任何具體的事情, 隻是簡單的啟動了一個會退出的事件循環.
#include <stdio.h>
#include <uv.h>
int main() {
uv_loop_t *loop = uv_loop_new();
printf("Now quitting.\n");
uv_run(loop, UV_RUN_DEFAULT);
return 0;
}
該程式啟動後就會直接退出, 因為你沒有事件可處理. 我們可以使用 libuv 提供了各種 API 來告知 libuv 我們感興趣的事件.
libuv 的預設事件循環(Default loop)
libuv 提供了一個預設的事件循環, 你可以通過 uv_default_loop
Note
node.js 使用預設事件循環作為它的主循環,如果你正在編寫 node.js 的綁定, 你應該意識到這一點.
螢幕(Watchers)
uv_TYPE_t 結構體的封裝, TYPE
typedef struct uv_loop_s uv_loop_t;
typedef struct uv_err_s uv_err_t;
typedef struct uv_handle_s uv_handle_t;
typedef struct uv_stream_s uv_stream_t;
typedef struct uv_tcp_s uv_tcp_t;
typedef struct uv_udp_s uv_udp_t;
typedef struct uv_pipe_s uv_pipe_t;
typedef struct uv_tty_s uv_tty_t;
typedef struct uv_poll_s uv_poll_t;
typedef struct uv_timer_s uv_timer_t;
typedef struct uv_prepare_s uv_prepare_t;
typedef struct uv_check_s uv_check_t;
typedef struct uv_idle_s uv_idle_t;
typedef struct uv_async_s uv_async_t;
typedef struct uv_process_s uv_process_t;
typedef struct uv_fs_event_s uv_fs_event_t;
typedef struct uv_fs_poll_s uv_fs_poll_t;
typedef struct uv_signal_s uv_signal_t;
所有螢幕的結構都是 uv_handle_t 的”子類”, 在 libuv 和本文中都稱之為句柄( handlers ).
螢幕由相應類型的初始化函數設定, 如下:
uv_TYPE_init(uv_TYPE_t*)
某些螢幕初始化函數的第一個參數為事件循環的句柄.
螢幕再通過調用如下類型的函數來設定事件回調函數并監聽相應事件:
uv_TYPE_start(uv_TYPE_t*, callback)
而停止監聽應調用如下類型的函數:
uv_TYPE_stop(uv_TYPE_t*)
當 libuv 所監聽事件發生後, 回調函數就會被調用. 應用程式特定的邏輯通常都是在回調函數中實作的, 例如, 定時器回調函數在發生逾時事件後也會被調用, 另外回調函被調用時傳入的相關參數都與特定類型的事件有關, 例如, IO 螢幕的回調函數在發生了IO事件後将會收到從檔案讀取的資料.
空轉(Idling)
#include <stdio.h>
#include <uv.h>
int64_t counter = 0;
void wait_for_a_while(uv_idle_t* handle, int status) {
counter++;
if (counter >= 10e6)
uv_idle_stop(handle);
}
int main() {
uv_idle_t idler;
uv_idle_init(uv_default_loop(), &idler);
uv_idle_start(&idler, wait_for_a_while);
printf("Idling...\n");
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
return 0;
}