ホームページ  >  記事  >  ウェブフロントエンド  >  Nodejs の非同期 I/O についての予備的な理解

Nodejs の非同期 I/O についての予備的な理解

青灯夜游
青灯夜游転載
2021-04-19 10:08:061988ブラウズ

この記事では、Nodejs の非同期 I/O について予備的に理解します。一定の参考値があるので、困っている友達が参考になれば幸いです。

Nodejs の非同期 I/O についての予備的な理解

# 「非同期」という用語は、実際には Node.js より前に生まれました。しかし、ほとんどの高級プログラミング言語では、非同期が発生することはまれです。多くの高級言語やオペレーティング プラットフォームの中で、Node は主要なプログラミング手法と設計コンセプトとして非同期を初めて使用しました。 [関連する推奨事項: "nodejs チュートリアル"]

非同期 I/O、イベント駆動型およびシングルスレッドが Node の基調を構成し、イベント駆動型および非同期 I/O 設計が構成されます。 Nginx と Node の概念は比較的似ています。 Nginx は純粋な C で書かれており、優れたパフォーマンスを備え、クライアントの接続を管理する強力な機能を備えていますが、依然としてさまざまな同期プログラミング言語による制限があります。しかし、Node は万能であり、クライアントからの大量の同時リクエストを処理するサーバーとして使用したり、ネットワーク内のさまざまなアプリケーションに同時リクエストを行うクライアントとしても使用できます。

非同期 I/O


なぜ非同期 I/O が Node で非常に重要なのか?これは、Node がネットワーク用に設計されているためです。 , 同時実行性は、現代のプログラミングの標準機能になっています。

ユーザー エクスペリエンス

「高パフォーマンス JavaScript」では、スクリプトの実行時間が 100 ミリ秒を超えると、ユーザーは、ページが停止しています。ページが応答を停止したと考えてください。 B/S モデルでは、ネットワーク速度の制限により、Web ページのリアルタイム エクスペリエンスに大きな問題が発生します。

Web ページが一時的にリソースを取得して同期的に取得する必要がある場合、JavaScript は実行を続行する前に、サーバーからリソースが完全に取得されるまで待つ必要があります。この間、UI は一時停止し、ユーザーのインタラクションに応答しません。このようなユーザー エクスペリエンスは非常に悪くなります。非同期リクエストでは、リソースのダウンロード中に JavaScript と UI の実行が待機状態にならず、ユーザーの操作に応答し続けることができます。

同様に、フロントエンドは非同期使用によって UI のブロックを排除できますが、フロントエンドがリソースを取得する速度はバックエンドの応答速度にも依存します。リソースが 2 つの異なる場所から返されたデータから得られる場合、最初のリソースは M ミリ秒を消費し、2 番目のリソースは N ミリ秒を消費します。同期方式を使用する場合、2 つのリソースを取得するのにかかる時間は M N ミリ秒です。非同期方式では、最初のリソースの取得は 2 番目のリソースの取得をブロックせず、消費される時間は max(M,N) です。

Web サイトまたはアプリケーションが拡大し続けるにつれて、M と N の値は直線的に増加し、非同期のパフォーマンスが同期よりも優れるようになります。

リソースの割り当て

ビジネス シナリオで完了する必要がある一連の無関係なタスクがあると仮定すると、次の 2 つの主流の方法があります。

    一度にシングルスレッドでシリアル実行
  • マルチスレッドの並列完了
マルチスレッドを作成するコストが並列実行よりも低い場合は、マルチスレッドを実行します。 -スレッド化が推奨されますが、マルチスレッドではスレッドの作成により多くの労力が必要です。また、実行中のスレッド コンテキスト切り替えのオーバーヘッドが比較的大きく、マルチスレッド プログラミングでは、ロックや状態同期などの問題が発生することがよくあります。

シングルスレッドによるタスクの順次実行の欠点はパフォーマンスにあり、タスクが少しでも遅いと、後続のコードの実行がブロックされてしまいます。コンピューター リソースでは、通常、I/O と CPU の計算は並行して実行できますが、同期プログラミング モデルでは、I/O が後続のタスクを待機するため、リソースが十分に活用されません。

ノードはシングル スレッドを使用してマルチスレッドのデッドロック、状態の同期、その他の問題を回避し、非同期 I/O を使用してシングル スレッドのブロックを回避し、CPU をより効率的に利用します。

非同期 I/O の実装


非同期 I/O は Node で最も広く使用されていますが、Node 独自のものではありません。


非同期 I/O とノンブロッキング I/O

コンピュータ カーネル I/O には、非同期/同期とブロッキング/ノンブロッキングの 2 つがあります。異なるもの。 。

オペレーティング システムの I/O 方式は、ブロッキングとノンブロッキングの 2 つだけです。ブロッキング I/O を呼び出す場合、アプリケーションは結果を返す前に I/O が完了するまで待つ必要があります。

ブロッキング I/O の特性の 1 つは、呼び出し後、呼び出しが終了する前にシステム カーネル レベルがすべての操作を完了するまで待機する必要があることです。 I/O をブロックすると、CPU が I/O を待つことになり、待ち時間が無駄になり、CPU の処理能力を十分に活用できなくなります。

パフォーマンスを向上させるために、カーネルはノンブロッキング I/O を提供します。ノンブロッキング I/O とブロッキング I/O の違いは、呼び出し直後に戻ることです。ノンブロッキング I/O が戻った後は、CPU タイム スライスを他の処理に使用できます。パフォーマンスの向上は明らかですが、I/O が完了していないため、すぐに返されるのはビジネス層が期待するデータではなく、現在の呼び出しステータスのみです。

完全なデータを取得するには、アプリケーションは I/O 操作を繰り返し呼び出して、完了したかどうかを確認する必要があります。操作が完了したかどうかを判断するために繰り返し呼び出しを行うこの手法は、

ポーリングと呼ばれます。

既存のポーリング テクノロジには、主に read、select、poll、epoll、kqueue が含まれます。ここでは、epoll のポーリング原理についてのみ説明します。

epoll は、Linux で最も効率的な I/O イベント通知メカニズムです。ポーリングを開始するときに、I/O イベントが検出されない場合、イベントが発生して起動するまでスリープ状態になります。クエリを走査する代わりにイベント通知と実行コールバック メソッドを実際に使用するため、CPU を無駄にせず、実行効率が高くなります。

Nodejs の非同期 I/O についての予備的な理解

ポーリング テクノロジは、完全なデータを確実に取得するためのノンブロッキング I/O のニーズを満たしますが、プログラムにとっては依然として一種の同期とみなされます。アプリケーションはまだ I/O が完全に戻るまで待機する必要がありますが、待機にはまだ時間がかかります。待機中、CPU はファイル記述子を反復処理するか、時間が発生するまでスリープするために使用されます。

現実的な非同期 I/O

一部のスレッドにブロッキング I/O またはノンブロッキング I/O とポーリング テクノロジを実行させ、スレッドが実行できるようにすることでデータ取得を完了します。計算を実行し、スレッド間の通信を通じて I/O から取得したデータを転送します。これにより、非同期 I/O が簡単に実装されます (ただし、これはシミュレートされています)

しかし、当初、ノードは *nix にありました。プラットフォームは libeio と libev を使用して実装しますI/O 部分を実装し、非同期 I/O を実装します。 Node v0.9.3 では、非同期 I/O を完了するためにスレッド プールが実装されています。

Windows の IOCP は、非同期メソッドの呼び出し、I/O 完了後の通知の待機、コールバックの実行など、Li Xiang の非同期 I/O をある程度提供します。ユーザーはポーリングを考慮する必要はありません。ただし、その内部は依然としてスレッド プールの原則に基づいており、異なるのは、これらのスレッド プールがシステム カーネルによって管理されることです。

Windows プラットフォームと *nix プラットフォームの違いにより、Node は抽象カプセル化層として libuv を提供するため、すべてのプラットフォーム互換性の判断はこの層によって完了し、上位ノードと下位ノードのカスタマイズが保証されます。スレッド プールと IOCP は完全に独立しています。

Nodejs の非同期 I/O についての予備的な理解

Node はシングルスレッドであるとよく言いますが、ここでのシングルスレッドとは、単一のスレッドで実行される JavaScript のことです。 Node には、*nix であっても Windows プラットフォームであっても、内部で I/O タスクを完了するスレッド プールがあります。

ノードの非同期 I/O


イベント ループ、オブザーバー、およびリクエスト オブジェクトにより、非同期 I/O リンク全体が完成します。

イベント ループ

イベント ループは Node 独自の実行モデルであり、コールバック関数が非常に一般的になります。

プロセスが開始されると、Node は while(true) に似たループを作成します。ループ本体が実行されるたびに、それを Tick と呼びます。各 Tick のプロセスは、処理対象のイベントがあるかどうかを確認し、存在する場合はイベントとそれに関連するコールバック関数を取得します。関連するコールバック関数が存在する場合は、それらを実行します。次に、次のループに入り、処理するイベントがなくなった場合はプロセスを終了します。

Nodejs の非同期 I/O についての予備的な理解

オブザーバー

各イベント ループには 1 つ以上のオブザーバーがあり、監視するイベントがあるかどうかを判断するために使用されます。処理されるプロセスでは、これらのオブザーバーに処理するイベントがあるかどうかを尋ねます。

Node では、イベントは主にネットワーク リクエスト、ファイル I/O などから発生します。これらの時間に対応するオブザーバーには、ファイル I/O オブザーバー、ネットワーク I/O オブザーバーなどが含まれます。オブザーバーがイベントを分類しました。

イベント ループは、典型的なプロデューサー/コンシューマー モデルです。非同期 I/O やネットワーク リクエストなどがイベントのプロデューサーであり、常にさまざまな種類のイベントを Node に提供し、これらのイベントは対応するオブザーバーに配信され、イベント ループはオブザーバーからイベントを取り出して処理します。

リクエスト オブジェクト

ノードの非同期 I/O 呼び出しの場合、コールバック関数は開発者によって呼び出されません。呼び出しを開始する JavaScript から I/O 操作を実行するカーネルへの移行プロセスでは、Request オブジェクト

という製品があり、以下では fs.open() メソッドが小さな例。

fs.open = function(path,flags,mode,callback){
    //...
    binding.open(pathModule._makeLong(path),
                    stringToFlags(flags),
                    mode,
                    callback);
}

fs.open() の機能は、指定されたパスとパラメータに基づいてファイルを開き、ファイル記述子を取得することです。これは、後続のすべての I/O 操作に対する最初の試行操作です。 JavaScript レベルのコードは、C コア モジュールを呼び出すことによって下位レベルの操作を実行します。

Nodejs の非同期 I/O についての予備的な理解

は、Node のコア モジュールを呼び出す JavaScript に取り組んでいます。コア モジュールは C モジュールを呼び出し、組み込みモジュールは libuv を通じてシステム コールを行います。これは古典的なものです。 Node でメソッドを呼び出します。ここで、libuv はカプセル化層として機能し、基本的に uv_fs_open() メソッドを呼び出す 2 つのプラットフォーム実装があります。 uv_fs_open() の呼び出しプロセス中に、JavaScript レイヤーから渡されたパラメーターと現在のメソッドがリクエスト オブジェクトにカプセル化され、コールバック関数がこのオブジェクトのプロパティに設定されます。オブジェクトがパッケージ化された後、オブジェクトはスレッド プールにプッシュされて実行を待ちます。

この時点で、JavaScript 呼び出しはすぐに戻り、JavaScript レベルによって開始された非同期呼び出しの最初のフェーズが終了します。 JavaScript スレッドは、現在のタスクに対して後続の操作を実行し続けることができます。

リクエスト オブジェクトは、非同期 I/O プロセスの重要な中間生成物です。I/O 操作の完了後の実行とコールバック処理を待機するためにスレッド プールに送信される状態を含む、すべての状態がこのオブジェクトに保存されます。

実行コールバック

リクエスト オブジェクトをアセンブルし、I/O スレッド プールに送信して実行を待機します。これは I/O の最初の部分のみを完了しますコールバック通知は最初の部分であり、その 2 です。

スレッド プール内の I/O 操作が呼び出された後、取得された結果は req->result 属性に格納され、PostQueueCompletionStatus() が呼び出されて、現在のオブジェクト操作が IOCP に通知されます。完成しました。

この時点で、非同期 I/O プロセス全体が完全に終了しました。

Nodejs の非同期 I/O についての予備的な理解

イベント ループ、オブザーバー、リクエスト オブジェクト、および I/O スレッド プールは、一緒になってノードの非同期 I/O モデルの基本要素を構成します。

まとめ

整理すると、非同期 I/O のキーワードとして、シングル スレッド、イベント ループ、オブザーバー、I/O スレッド プールがいくつか抽出できます。シングルスレッドとスレッドプールは少し矛盾しているように思えます。 JavaScript はシングルスレッドであるため、マルチコア CPU を最大限に活用できないことは容易に理解できます。実際、Node では、JavaScript がシングルスレッドであることを除いて、Node 自体は実際にはマルチスレッドですが、I/O スレッドが使用する CPU は少なくなります。また、ユーザーコードは並列実行できないことを除き、すべての I/O (ディスク I/O、ネットワーク I/O など) は並列実行できます。

プログラミング関連の知識について詳しくは、プログラミング ビデオをご覧ください。 !

以上がNodejs の非同期 I/O についての予備的な理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。