「非同期」という用語の大人気は、JavaScriptとAjaxでWebを席巻したWeb 2.0の波にありました。しかし、非同期はほとんどの高レベルのプログラミング言語ではまれです。 PHPはこの機能を最もよく反映しています。非同期にブロックするだけでなく、複数のスレッドを提供しません。 PHPは同期ブロッキング方法で実行されます。このような利点は、プログラマーがビジネスロジックを順番に記述するのに有益ですが、複雑なネットワークアプリケーションでは、ブロックすると、より並行しません。
サーバー側では、I/Oは非常に高価であり、分散I/Oはより高価です。バックエンドがリソースに迅速に応答できる場合にのみ、フロントエンドエクスペリエンスが良くなります。 node.jsは、メインプログラミング方法および設計コンセプトとして非同期を使用する最初のプラットフォームです。非同期I/O、イベント駆動型およびシングルスレッドを伴う、それらはノードのトーンを形成します。この記事では、ノードが非同期I/Oをどのように実装するかを紹介します。
1。基本概念
「async」と「nonblocking」は同じものに聞こえ、実際の結果の観点から、どちらも並列性の目的を達成します。しかし、コンピューターカーネルI/Oの観点から見ると、ブロッキングと非ブロッキングの2つの方法しかありません。したがって、非同期/同期とブロッキング/ノンブロッキングは、実際には2つの異なるものです。
1.1ブロックI/Oおよび非ブロッキングI/O
I/Oのブロックの特徴の1つは、呼び出し後、コールが完了する前にすべての操作がシステムカーネルレベルで完了するまで待つ必要があることです。ディスク上のファイルを読み取る例として、この呼び出しはシステムカーネルがディスク検索を完了し、データを読み取り、データをメモリにコピーすると終了します。
I/Oをブロックすると、CPUがI/Oを待ち、待ち時間を無駄にし、CPUの処理能力を完全に利用できません。非ブロッキングI/Oの特徴は、コールの直後に戻ることであり、CPU時間スライスを使用して、返品後に他のトランザクションを処理できることです。完全なI/Oが完了していないため、すぐに返されるのは、ビジネスレイヤーで予想されるデータではなく、現在の呼び出しのステータスだけです。完全なデータを取得するには、アプリケーションがI/O操作を繰り返し呼び出して、完了したかどうか(つまり、ポーリング)を確認する必要があります。ポーリングテクニックには次のものが必要です。
1.Read:繰り返しの呼び出しでI/Oステータスを確認することは、最もオリジナルで最も低いパフォーマンス方法です
2.選択:改善のための改善、ファイル記述子のイベントステータスを判断します。欠点は、ファイル記述子の最大数が制限されていることです。
3.poll:リンクされたリストを使用して最大数の制限を回避するための選択の改善ですが、記述子が多い場合、パフォーマンスはまだ非常に低いです
4.EPOLL:ポーリング中にI/Oイベントがチェックされない場合、イベントが発生して目覚めるまで眠ります。これは、Linuxの下で最も効率的なI/Oイベント通知メカニズムです。
ポーリングは、完全なデータ収集を確保するために非ブロッキングI/Oの必要性を満たしていますが、アプリケーションでは、I/Oが完全に戻るのを待つ必要があるため、一種の同期としてカウントできます。待機中、CPUはファイル記述子の状態を通過するために使用されるか、イベントが発生するのを待っている冬眠します。
1.2理想と現実における非同期I/O
完全な非同期I/Oは、非ブロッキングコールを開始するアプリケーションであり、ポーリングなしで次のタスクを直接処理できる必要があります。I/Oが完了した後、信号またはコールバックを介してアプリケーションにデータを渡すだけです。
現実には、非同期I/Oは、異なるオペレーティングシステムの下で異なる実装を持っています。たとえば、 *nixプラットフォームはカスタムスレッドプールを採用し、WindowsプラットフォームはIOCPモデルを採用します。ノードは、Libuvを抽象カプセル化レイヤーとして提供して、プラットフォームの互換性の判断をカプセル化し、上部ノードと下部プラットフォームの非同期I/Oの実装が独立していることを保証します。ノードはシングルスレッドであることをよく言及していることを強調する必要があります。これは、JavaScriptの実行が単一のスレッドにあることを意味し、ノード内で実際にI/Oタスクを完了する他のスレッドプールがあることを意味します。
2。ノードの非同期I/O
2.1イベントループ
ノードの実行モデルは、実際にはイベントループです。プロセスが開始されると、ノードは無限のループを作成し、ループ本体を実行する各プロセスがティックになります。各ティックプロセスは、処理を待っているイベントがあるかどうかを確認することです。その場合、イベントと関連するコールバック関数が削除されます。関連するコールバック関数がある場合、それらは実行され、次のループが入力されます。イベント処理がもうない場合は、プロセスを終了します。
2.2オブザーバー
各イベントループにはいくつかのオブザーバーがあり、これらのオブザーバーに尋ねることで、処理するイベントがあるかどうかを判断できます。イベントループは、典型的な生産者/消費者モデルです。ノードでは、イベントは主にネットワークリクエスト、ファイルI/Oなどから来ます。これらのイベントには、対応するネットワークI/Oオブザーバー、ファイルI/Oオブザーバーなどがあります。イベントループは、オブザーバーからイベントを取り出して処理します。
2.3オブジェクトを要求します
JavaScriptからI/O操作を実行するカーネルへの移行中に、リクエストオブジェクトと呼ばれる中間製品があります。 Windowsでfs.open()の最も単純な方法を使用して(ファイルを開き、指定されたパスとパラメーターに従ってファイル記述子を取得)、JSコールから内蔵モジュールまで、Libuvを介したシステム呼び出しは、実際にはuv_fs_open()メソッドと呼ばれます。呼び出しプロセス中に、FSREQWRAPリクエストオブジェクトが作成され、JSレイヤーから渡されたパラメーターとメソッドがこのリクエストオブジェクトにカプセル化されます。私たちが最も心配しているコールバック関数は、このオブジェクトのoncompete_symプロパティに設定されています。オブジェクトがラップされたら、FSREQWRAPオブジェクトをスレッドプールに押し込み、実行を待ちます。
この時点で、JSコールはすぐに戻り、JSスレッドは後続の操作を引き続き実行できます。現在のI/O操作は、スレッドプールでの実行を待っており、非同期コールの最初の段階が完了します。
2.4コールバックを実行します
コールバック通知は、非同期I/Oの第2フェーズです。スレッドプールでのI/O操作が呼び出された後、取得した結果が保存され、IOCPは現在のオブジェクト操作が完了し、スレッドがスレッドプールを返すことを通知されます。各ダニの実行中、イベントループのI/Oオブザーバーは、関連する方法を呼び出して、スレッドプールに完了した要求があるかどうかを確認します。存在する場合、リクエストオブジェクトはI/Oオブザーバーのキューに追加され、イベントとして処理されます。
3。非I/O非同期API
また、タイマーSettimeout()、setInterval()、process.nexttick()、Setimmdiate()など、ノードのI/Oに関連しない非同期APIもあり、すぐにタスクを非同期に実行します。
3.1タイマーAPI
Settimeout()およびSetInterval()のブラウザ側のAPIは一貫しています。それらの実装の原則は非同期I/Oに似ていますが、I/Oスレッドプールの参加は必要ありません。タイマーAPIを呼び出すことによって作成されたタイマーは、タイマーオブザーバー内の赤と黒の木に挿入されます。各イベントループのティックは、赤と黒の木からタイマーオブジェクトを繰り返し、時間が超えたかどうかを確認します。それを超えると、イベントが形成され、コールバック関数がすぐに実行されます。タイマーの主な問題は、そのタイミング時間が特に正確ではないことです(ミリ秒、許容範囲内)。
3.2非同期タスク実行API
ノードが表示される前に、多くの人がこれを呼び出すために、すぐにタスクを非同期に実行するために次のように呼びます。
コードコピーは次のとおりです。
setimeout(function(){
// todo
}、0);
イベントループの特性により、タイマーは十分に正確ではなく、赤と黒の木を使用するにはタイマーの使用が必要であり、さまざまな動作時間の複雑さはO(log(n))です。 process.nexttick()メソッドは、コールバック関数をキューに配置し、次のラウンドのティックで実行して実行します。複雑さはO(1)であり、より効率的です。
さらに、上記の方法と同様のSetimmediate()メソッドがあり、どちらもコールバック関数の実行が遅れています。ただし、前者は後者よりも優先度が高いため、イベントループはオブザーバーを順番にチェックしているためです。さらに、前のコールバック関数は配列に保存され、ティックの各ラウンドは配列内のすべてのコールバック関数を実行します。後者の結果はリンクリストに保存され、各ラウンドのティックは1つのコールバック関数のみを実行します。
4。イベント駆動型および高性能サーバー
前の例は、ノードが非同期I/Oをどのように実装するかを示しています。実際、ノードはネットワークソケット処理に非同期I/Oを適用します。これは、ノードがWebサーバーを構築するための基礎でもあります。クラシックサーバーモデルは次のとおりです。
1。同期:一度に1つのリクエストのみを処理でき、残りのリクエストは待機状態にあります
2。プロセスごと/リクエストごと:各リクエストの1つのプロセスを開始しますが、システムリソースは制限されており、スケーラビリティがありません。
3。スレッドごと/リクエストごと:リクエストごとに1つのスレッドを開始します。スレッドはプロセスよりも軽いですが、各スレッドは一定量のメモリを占有します。大規模な同時リクエストが到着すると、メモリはすぐになくなります。
有名なApacheは、スレッドごと/レクエストごとのフォームを採用しているため、高い並行性に対処することが困難です。ノードは、イベント駆動型のメソッドを介してリクエストを処理します。これにより、スレッドの作成と破壊のオーバーヘッドを保存できます。同時に、タスクのスケジュール時にオペレーティングシステムのスレッドが少なく、コンテキストの切り替えのコストも非常に低くなります。ノードは、多数の接続があっても、整然とした方法でリクエストを処理できます。
また、よく知られているサーバーNginxは、マルチスレッド方法を放棄し、ノードと同じイベント駆動型メソッドを採用しています。これで、NginxはApacheを交換するために大いに進んでいます。 Nginxは純粋なCで記述され、高性能ですが、逆プロキシまたはロードバランスなどに使用されるWebサーバーにのみ適しています。ノードはNginxと同じ機能を構築でき、さまざまな特定のビジネスを処理することもできます。また、独自のパフォーマンスも優れています。実際のプロジェクトでは、それらを組み合わせてアプリケーションの最高のパフォーマンスを実現できます。