MENU

libuvとは|Node.jsを支えるクロスプラットフォーム非同期I/O

libuv アイキャッチ
libuv

libuvはNode.jsの非同期I/Oを担うために2011年に切り出されたC言語ライブラリで、もともとはWindows対応のためにLinux専用だったlibevとlibeioを置き換える目的で書かれました。イベントループ、非同期ファイルシステム操作、TCP/UDP、子プロセス、スレッドプール、シグナルハンドリングなど、OS依存の難しい部分を抽象化し、単一のAPIで扱えるようにします。Node.jsだけでなく、JuliaやLuvit、CMake開発のninjaなど、多数のプロジェクトが基盤として採用しています。

目次

この記事の目次

  1. 誕生の経緯とNode.jsとの関係
  2. イベントループとフェーズ構造
  3. スレッドプールとブロッキング処理の逃がし方
  4. Node.js以外への波及
  5. まとめ

誕生の経緯とNode.jsとの関係

誕生の経緯とNode.jsとの関係

Node.jsは初期、Linux固有のイベント駆動ライブラリlibevと、ファイルI/O用のlibeioを直接利用していました。しかしWindowsはこれらの抽象化にうまく嵌まらず、Joyent社時代のNode.jsチームはクロスプラットフォーム対応のために新たな共通基盤を必要としました。Ben Noordhuis氏らが中心となって生まれたのがlibuvで、Linuxではepoll、macOSではkqueue、WindowsではI/O完了ポート(IOCP)という、OSごとに異なる非同期機構を統一的なAPIに包んでいます。

libuvが切り出されたことでNode.jsは、Windowsを含む主要OSで等しく高性能な非同期I/Oを提供できるようになりました。またライブラリ単体として公開されたことで、Node.js以外のソフトウェアも同じ基盤を再利用できるようになり、結果としてC/C++エコシステムにおける標準的なイベントループ実装の一つにまで成長しました。

イベントループとフェーズ構造

イベントループとフェーズ構造

libuvのイベントループは単一スレッドで動作し、タイマー、ペンディングコールバック、ポーリング、チェック、クローズコールバックといった複数のフェーズを順番に処理します。各フェーズには専用のキューがあり、その時点で実行可能なコールバックが順に呼び出されます。Node.jsのprocess.nextTickやsetImmediateの挙動の違いは、このフェーズ構造を理解すると初めて腑に落ちるでしょう。

ポーリングフェーズではOSのepoll/kqueue/IOCPに対してブロッキングwaitを行い、I/Oイベントが到着するか、次のタイマーが満期になるまで効率よく休止します。これにより、Node.jsアプリは少ないスレッドで多数のネットワーク接続を捌くことができ、C10K問題に対する現実的な解として広く受け入れられました。

スレッドプールとブロッキング処理の逃がし方

スレッドプールとブロッキング処理の逃がし方

libuvの興味深い点は、純粋なイベント駆動だけでなく、内部にスレッドプールを抱えていることです。デフォルトで4スレッド、UV_THREADPOOL_SIZE環境変数で調整可能なこのプールは、ファイル読み書きやDNS解決、cryptoモジュールのpbkdf2など、OSが真の非同期APIを提供していない処理を別スレッドで実行して結果をイベントループに戻すために使われます。

そのため、CPUバウンドな処理をうっかりイベントループ本体で回すと、他のすべてのコールバックが詰まる、という典型的なNode.jsのアンチパターンが発生します。回避策としては、worker_threadsやnative addon経由でlibuvのwork queue APIを利用し、重い処理をスレッドプールに逃がす、あるいは別プロセスに分離する設計が現実的です。libuvの内部構造を知っていると、こうした判断がぶれずに行えます。

Node.js以外への波及

Node.js以外への波及

libuvはNode.jsの心臓部であると同時に、独立したC言語ライブラリとしての価値も大きい存在です。Julia言語のランタイムはlibuvをタスクスケジューラの一部として採用しており、並列数値計算とI/O待ちの両立を支えています。Luvitやneovimもイベント駆動部分にlibuvを使っており、「クロスプラットフォームで非同期イベントループが欲しい」というニーズの定番解になりました。

対して、より新しいランタイムであるBunはZigで独自のイベントループを実装し、libuvに依存しません。これはlibuvの設計が古いというよりも、「Node.jsの後方互換性」と「ゼロから最適化された設計」の間で意図的な選択をした結果と見るべきでしょう。Node.js本体側でも、io_uringへの段階的対応など、libuvを通じてLinuxの新しい非同期I/O APIに追随する動きが続いています。

まとめ

libuvはWindowsを含む主要OSの差異を吸収しつつ、イベントループとスレッドプールを提供するC言語ライブラリで、Node.jsの非同期I/Oを根本から支えています。フェーズ構造とプールの存在を理解すると、Node.jsの挙動チューニングが格段にやりやすくなります。Node.js以外にもJuliaやneovimなどで採用が広がっている、地味ながら極めて重要な基盤です。

※本記事はIT用語辞典の手書きドラフトです。公開前に最新情報・出典を確認のうえ加筆修正してください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次