検索
ホームページウェブフロントエンドjsチュートリアルlibuv とは何か、libuv でのイベント ポーリングの簡単な分析 (ノード コアの依存関係)

この記事では、Node の中心的な依存関係である libuv を理解し、libuv とは何か、および libuv でのイベント ポーリングについて紹介します。

libuv とは何か、libuv でのイベント ポーリングの簡単な分析 (ノード コアの依存関係)

Node.js のことになると、ほとんどのフロントエンド エンジニアはこれに基づいてサーバーを開発することを考えると思います。 JavaScriptを言語としてマスターする フルスタックエンジニアになる しかし、実はNode.jsの意味はそれだけではありません。

多くの高級言語では、実行権限がオペレーティング システムに到達しますが、ブラウザ側で実行される JavaScript は例外です。ブラウザによって作成されたサンドボックス環境は、フロントエンドを閉じます。プログラミングの世界の象牙の塔に住むエンジニア。しかし、Node.jsの登場によりこの欠点は補われ、フロントエンドエンジニアもコンピュータの世界の底辺に到達できるようになりました。

つまり Nodejs フロントエンド エンジニアにとっての重要性は、フルスタックの開発機能を提供することだけではなく、より重要なことに、フロントエンド用のコンピューターの基盤となる世界への扉を開くことです。エンジニア。この記事では、Node.js の実装原則を分析することでこの扉を開きます。

Node.js ソース コードの構造

Node.js ソース コード ウェアハウスの /deps ディレクトリには、C 言語で書かれたモジュール (libuv、 V8)とJavaScript言語で記述したモジュール(acorn、acorn-pluginsなど)は下図のとおりです。

libuv とは何か、libuv でのイベント ポーリングの簡単な分析 (ノード コアの依存関係)

  • #acorn: JavaScript で書かれた軽量の JavaScript パーサー。
  • acorn-plugins: acorn の拡張モジュール。acorn がクラス宣言などの ES6 機能解析をサポートできるようにします。
  • brotli: C 言語で書かれた Brotli 圧縮アルゴリズム。
  • cares: 非同期 DNS リクエストを処理するために、C 言語で記述された「c-ares」として記述する必要があります。
  • histogram: ヒストグラム生成関数を実装するために C 言語で記述されています。
  • icu-small: C 言語で書かれ、Node.js 用にカスタマイズされた ICU (International Components for Unicode) ライブラリ。Unicode を操作するためのいくつかの関数が含まれています。
  • llhttp: C 言語で書かれた軽量の http パーサー。
  • nghttp2/nghttp3/ngtcp2: HTTP/2、HTTP/3、TCP/2 プロトコルを処理します。
  • node-inspect: Node.js プログラムが CLI デバッグ デバッグ モードをサポートできるようにします。
  • npm: JavaScript で書かれた Node.js モジュール マネージャー。
  • openssl: C 言語で書かれた暗号化関連モジュール。TLS モジュールと暗号化モジュールの両方で使用されます。
  • uv: C 言語で書かれ、ノンブロッキング I/O 操作を使用して、Node.js にシステム リソースにアクセスする機能を提供します。
  • uvwasi: C 言語で書かれ、WASI システム コール API を実装します。
  • v8: C 言語、JavaScript エンジンで書かれています。
  • zlib: 高速圧縮のために、Node.js は zlib を使用して、同期、非同期、およびデータ ストリームの圧縮および解凍インターフェイスを作成します。

最も重要なものは、v8 ディレクトリと uv ディレクトリに対応するモジュールです。 V8 自体には非同期で実行する機能はありませんが、ブラウザー内の他のスレッドの助けを借りて実装されています。js がシングルスレッドであるとよく言われるのは、その解析エンジンが同期解析コードのみをサポートしているためです。 ただし、Node.js では、非同期実装は主に libuv に依存しているため、libuv の実装原理の分析に焦点を当てましょう。

libuv とは

#libuv は、複数のプラットフォームをサポートする C で書かれた非同期 I/O ライブラリであり、主に I/O 操作がブロックされやすい問題を解決します。 元々は Node.js で使用するために特別に開発されましたが、後に Luvit、Julia、pyuv などの他のモジュールでも使用されるようになりました。以下の図はlibuvの構造図です。

libuv とは何か、libuv でのイベント ポーリングの簡単な分析 (ノード コアの依存関係)

#libuv には 2 つの非同期実装メソッドがあります。これらは、上の図の左右の黄色のボックスで選択された 2 つの部分です。

左側の部分はネットワーク I/O モジュールであり、プラットフォームごとに異なる実装メカニズムがあり、Linux システムでは epoll を通じて実装され、OSX およびその他の BSD システムでは KQueue が使用され、SunOS システムではイベント ポートが使用されます。 Windows システムは IOCP を使用します。これにはオペレーティング システムの基盤となる API が関係するため、理解するのがさらに複雑になるため、ここでは紹介しません。

右側の部分には、ファイル I/O モジュール、DNS モジュール、およびスレッド プールを介して非同期操作を実装するユーザー コードが含まれています。ファイル I/O はネットワーク I/O とは異なります。libuv はシステムの基礎となる API に依存せず、代わりに、グローバル スレッド プールでファイル I/O 操作のブロックを実行します。

libuv でのイベント ポーリング

次の図は libuv 公式 Web サイトに掲載されているイベント ポーリングのワークフロー図ですので、コードと合わせて分析してみましょう。

libuv とは何か、libuv でのイベント ポーリングの簡単な分析 (ノード コアの依存関係)

libuv イベント ループのコア コードは uv_run() 関数に実装されています。以下は Unix システムでのコア コードの一部です。 C言語で書かれていますが、JavaScriptと同じような高級言語なので、理解することはそれほど難しくありません。最大の違いはアスタリスクと矢印ですが、アスタリスクは単に無視して問題ありません。たとえば、関数パラメータの uv_loop_t* ループは、uv_loop_t 型の変数ループとして理解できます。矢印「→」はピリオド「.」として理解でき、例えばloop→stop_flagはloop.stop_flagとして理解できる。

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  ... 
r = uv__loop_alive(loop);
if (!r) uv__update_time(loop);
while (r != 0 && loop - >stop_flag == 0) {
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);...uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);...
}...
}

uv__loop_alive

この関数は、イベント ポーリングを続行するかどうかを決定するために使用されます。ループ オブジェクトにアクティブなタスクがない場合は、 0 を返してループを終了します。

C言語では、この「タスク」には「ハンドル」という専門的な名前が付けられており、タスクを指す変数として理解できます。ハンドルは、リクエストとハンドルの 2 つのカテゴリに分類でき、それぞれ短いライフ サイクルのハンドルと長いライフ サイクルのハンドルを表します。具体的なコードは次のとおりです。

static int uv__loop_alive(const uv_loop_t * loop) {
    return uv__has_active_handles(loop) || uv__has_active_reqs(loop) || loop - >closing_handles != NULL;
}

uv__update_time

時間関連のシステムコールの数を減らすために、この関数が使用されます。現在のシステム時間をキャッシュします。精度は非常に高く、ナノ秒レベルに達しますが、単位は依然としてミリ秒です。

具体的なソース コードは次のとおりです。

UV_UNUSED(static void uv__update_time(uv_loop_t * loop)) {
    loop - >time = uv__hrtime(UV_CLOCK_FAST) / 1000000;
}

uv__run_timers

setTimeout の時間しきい値に達したコールバックを実行します。 () および setInterval() 関数。この実行プロセスは for ループ トラバーサルによって実装されます。以下のコードからわかるように、タイマー コールバックは最小ヒープ構造のデータに格納されます。最小ヒープが空になるか、時間のしきい値に達しない場合に終了します。サイクル。

タイマー コールバック関数を実行する前にタイマーを削除してください。繰り返しが設定されている場合は、再度最小ヒープに追加する必要があり、その後タイマー コールバックが実行されます。

具体的なコードは次のとおりです。

void uv__run_timers(uv_loop_t * loop) {
    struct heap_node * heap_node;
    uv_timer_t * handle;
    for (;;) {
        heap_node = heap_min(timer_heap(loop));
        if (heap_node == NULL) break;
        handle = container_of(heap_node, uv_timer_t, heap_node);
        if (handle - >timeout > loop - >time) break;
        uv_timer_stop(handle);
        uv_timer_again(handle);
        handle - >timer_cb(handle);
    }
}

uv__run_pending

pending_queue に格納されているすべての I/O コールバック関数を走査します。 when pending_queue が空の場合は 0 を返し、それ以外の場合は pending_queue でコールバック関数を実行した後に 1 を返します。

コードは次のとおりです:

static int uv__run_pending(uv_loop_t * loop) {
    QUEUE * q;
    QUEUE pq;
    uv__io_t * w;
    if (QUEUE_EMPTY( & loop - >pending_queue)) return 0;
    QUEUE_MOVE( & loop - >pending_queue, &pq);
    while (!QUEUE_EMPTY( & pq)) {
        q = QUEUE_HEAD( & pq);
        QUEUE_REMOVE(q);
        QUEUE_INIT(q);
        w = QUEUE_DATA(q, uv__io_t, pending_queue);
        w - >cb(loop, w, POLLOUT);
    }
    return 1;
}

uvrun_idle / uvrun_prepare / uv__run_check

これら 3 つの関数はすべて、マクロ関数 UV_LOOP_WATCHER_DEFINE 定義の場合、マクロ関数はコード テンプレート、または関数を定義するために使用される関数として理解できます。マクロ関数は3回呼び出され、それぞれprepare、check、idleという名前パラメータの値が渡され、同時にuvrun_idle、uvrun_prepare、uv__run_checkの3つの関数が定義されています。

したがって、それらの実行ロジックは一貫しています。すべてループして、先入れ先出しの原則に従ってキューのloop->name##_handles内のオブジェクトを取り出し、対応するコールバックを実行します。関数。

#define UV_LOOP_WATCHER_DEFINE(name, type)
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);
  }
}
UV_LOOP_WATCHER_DEFINE(prepare, PREPARE) 
UV_LOOP_WATCHER_DEFINE(check, CHECK) 
UV_LOOP_WATCHER_DEFINE(idle, IDLE)

uv__io_poll

##uv__io_poll は主に I/O 操作をポーリングするために使用されます。具体的な実装はオペレーティング システムによって異なりますが、ここでは Linux システムを例として分析します。

uv__io_poll 関数には多くのソース コードがあり、コアは 2 つのループ コードであり、コードの一部は次のとおりです:

void uv__io_poll(uv_loop_t * loop, int timeout) {
    while (!QUEUE_EMPTY( & loop - >watcher_queue)) {
        q = QUEUE_HEAD( & loop - >watcher_queue);
        QUEUE_REMOVE(q);
        QUEUE_INIT(q);
        w = QUEUE_DATA(q, uv__io_t, watcher_queue);
        e.events = w - >pevents;
        e.data.fd = w - >fd;
        if (w - >events == 0) op = EPOLL_CTL_ADD;
        else op = EPOLL_CTL_MOD;
        if (epoll_ctl(loop - >backend_fd, op, w - >fd, &e)) {
            if (errno != EEXIST) abort();
            if (epoll_ctl(loop - >backend_fd, EPOLL_CTL_MOD, w - >fd, &e)) abort();
        }
        w - >events = w - >pevents;
    }
    for (;;) {
        for (i = 0; i < nfds; i++) {
            pe = events + i;
            fd = pe - >data.fd;
            w = loop - >watchers[fd];
            pe - >events &= w - >pevents | POLLERR | POLLHUP;
            if (pe - >events == POLLERR || pe - >events == POLLHUP) pe - >events |= w - >pevents & (POLLIN | POLLOUT | UV__POLLRDHUP | UV__POLLPRI);
            if (pe - >events != 0) {
                if (w == &loop - >signal_io_watcher) have_signals = 1;
                else w - >cb(loop, w, pe - >events);
                nevents++;
            }
        }
        if (have_signals != 0) loop - >signal_io_watcher.cb(loop, &loop - >signal_io_watcher, POLLIN);
    }...
}

while ループでは、オブザーバー キュー watcher_queue を走査します。そして、イベントとファイル記述子を取り出します。その値をイベント オブジェクト e に割り当てます。その後、epoll_ctl 関数を呼び出して、epoll イベントを登録または変更します。

for ループでは、epoll で待機しているファイル記述子が最初に取得されて nfds に割り当てられ、次に nfds が走査されてコールバック関数が実行されます。

__run_closed_handles

クローズされるのを待っているキューを走査し、ストリーム、tcp、udp などのハンドルを閉じてから、 close_cb ハンドルに対応します。コードは次のとおりです:

static void uv__run_closing_handles(uv_loop_t * loop) {
    uv_handle_t * p;
    uv_handle_t * q;
    p = loop - >closing_handles;
    loop - >closing_handles = NULL;
    while (p) {
        q = p - >next_closing;
        uv__finish_close(p);
        p = q;
    }
}

process.nextTick と Promise

process.nextTick と Promise はどちらも非同期 API ですが、イベント ポーリングの一部ではなく、独自のタスク キューを持っています。イベントループの各ステップが完了した後に実行されます。したがって、これら 2 つの非同期 API を使用する場合は、受信コールバック関数で長いタスクや再帰が実行されると、イベント ポーリングがブロックされ、I/O 操作が「枯渇」することに注意する必要があります。

次のコードは、fs.readFile のコールバック関数の実行に失敗する prcoess.nextTick への再帰呼び出しの例です。

fs.readFile(&#39;config.json&#39;, (err, data) = >{...
}) const traverse = () = >{
    process.nextTick(traverse)
}

この問題を解決するには、代わりに setImmediate を使用します。これは、setImmediate がイベント ポーリングでコールバック関数キューを実行するためです。 process.nextTick タスク キューは、Promise タスク キューよりも高い優先順位を持っています。具体的な理由については、次のコードを参照してください:

function processTicksAndRejections() {
    let tock;
    do {
        while (tock = queue.shift()) {
            const asyncId = tock[async_id_symbol];
            emitBefore(asyncId, tock[trigger_async_id_symbol], tock);
            try {
                const callback = tock.callback;
                if (tock.args === undefined) {
                    callback();
                } else {
                    const args = tock.args;
                    switch (args.length) {
                    case 1:
                        callback(args[0]);
                        break;
                    case 2:
                        callback(args[0], args[1]);
                        break;
                    case 3:
                        callback(args[0], args[1], args[2]);
                        break;
                    case 4:
                        callback(args[0], args[1], args[2], args[3]);
                        break;
                    default:
                        callback(...args);
                    }
                }
            } finally {
                if (destroyHooksExist()) emitDestroy(asyncId);
            }
            emitAfter(asyncId);
        }
        runMicrotasks();
    } while (! queue . isEmpty () || processPromiseRejections());
    setHasTickScheduled(false);
    setHasRejectionToWarn(false);
}

processTicksAndRejections() 関数からわかるように、最初にコールバック関数while ループでキューの queue を取り出し、process.nextTick でこのキュー内のコールバック関数を追加します。 while ループが終了すると、runMicrotasks() 関数が呼び出され、Promise コールバック関数が実行されます。

概要

libuv に依存する Node.js のコア構造は 2 つの部分に分けることができます。1 つの部分はネットワーク I/O です。基盤となる実装は、次のとおり異なるシステム API に依存します。他の部分は、ファイル I/O、DNS、およびユーザー コードがスレッド プールによって処理されることです。

libuv の非同期操作を処理するための中心的なメカニズムはイベント ポーリングです。イベント ポーリングはいくつかのステップに分かれています。一般的な操作は、キュー内のコールバック関数を走査して実行することです。

最後に、非同期 API process.nextTick と Promise はイベント ポーリングに属さないことに言及しました。不適切に使用すると、イベント ポーリングがブロックされます。解決策の 1 つは、代わりに setImmediate を使用することです。

ノード関連の知識の詳細については、nodejs チュートリアル を参照してください。

以上がlibuv とは何か、libuv でのイベント ポーリングの簡単な分析 (ノード コアの依存関係)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事は掘金社区で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。
JavaScriptとWeb:コア機能とユースケースJavaScriptとWeb:コア機能とユースケースApr 18, 2025 am 12:19 AM

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScriptエンジンの理解:実装の詳細JavaScriptエンジンの理解:実装の詳細Apr 17, 2025 am 12:05 AM

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

Python vs. JavaScript:学習曲線と使いやすさPython vs. JavaScript:学習曲線と使いやすさApr 16, 2025 am 12:12 AM

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

Python vs. JavaScript:コミュニティ、ライブラリ、リソースPython vs. JavaScript:コミュニティ、ライブラリ、リソースApr 15, 2025 am 12:16 AM

PythonとJavaScriptには、コミュニティ、ライブラリ、リソースの観点から、独自の利点と短所があります。 1)Pythonコミュニティはフレンドリーで初心者に適していますが、フロントエンドの開発リソースはJavaScriptほど豊富ではありません。 2)Pythonはデータサイエンスおよび機械学習ライブラリで強力ですが、JavaScriptはフロントエンド開発ライブラリとフレームワークで優れています。 3)どちらも豊富な学習リソースを持っていますが、Pythonは公式文書から始めるのに適していますが、JavaScriptはMDNWebDocsにより優れています。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

C/CからJavaScriptへ:すべてがどのように機能するかC/CからJavaScriptへ:すべてがどのように機能するかApr 14, 2025 am 12:05 AM

C/CからJavaScriptへのシフトには、動的なタイピング、ゴミ収集、非同期プログラミングへの適応が必要です。 1)C/Cは、手動メモリ管理を必要とする静的に型付けられた言語であり、JavaScriptは動的に型付けされ、ごみ収集が自動的に処理されます。 2)C/Cはマシンコードにコンパイルする必要がありますが、JavaScriptは解釈言語です。 3)JavaScriptは、閉鎖、プロトタイプチェーン、約束などの概念を導入します。これにより、柔軟性と非同期プログラミング機能が向上します。

JavaScriptエンジン:実装の比較JavaScriptエンジン:実装の比較Apr 13, 2025 am 12:05 AM

さまざまなJavaScriptエンジンは、各エンジンの実装原則と最適化戦略が異なるため、JavaScriptコードを解析および実行するときに異なる効果をもたらします。 1。語彙分析:ソースコードを語彙ユニットに変換します。 2。文法分析:抽象的な構文ツリーを生成します。 3。最適化とコンパイル:JITコンパイラを介してマシンコードを生成します。 4。実行:マシンコードを実行します。 V8エンジンはインスタントコンピレーションと非表示クラスを通じて最適化され、Spidermonkeyはタイプ推論システムを使用して、同じコードで異なるパフォーマンスパフォーマンスをもたらします。

ブラウザを超えて:現実世界のJavaScriptブラウザを超えて:現実世界のJavaScriptApr 12, 2025 am 12:06 AM

現実世界におけるJavaScriptのアプリケーションには、サーバー側のプログラミング、モバイルアプリケーション開発、モノのインターネット制御が含まれます。 2。モバイルアプリケーションの開発は、ReactNativeを通じて実行され、クロスプラットフォームの展開をサポートします。 3.ハードウェアの相互作用に適したJohnny-Fiveライブラリを介したIoTデバイス制御に使用されます。

next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)next.jsを使用してマルチテナントSaaSアプリケーションを構築する(バックエンド統合)Apr 11, 2025 am 08:23 AM

私はあなたの日常的な技術ツールを使用して機能的なマルチテナントSaaSアプリケーション(EDTECHアプリ)を作成しましたが、あなたは同じことをすることができます。 まず、マルチテナントSaaSアプリケーションとは何ですか? マルチテナントSaaSアプリケーションを使用すると、Singの複数の顧客にサービスを提供できます

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

Eclipse を SAP NetWeaver アプリケーション サーバーと統合します。

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール