この記事では、Node での非同期実装とイベント ドライブについて説明します。お役に立てば幸いです。
ノードの特徴
コンピュータの一部のタスクは一般に 2 つのカテゴリに分類でき、1 つのカテゴリは IO と呼ばれます。集中的なものは、コンピューティング集中型と呼ばれます。コンピューティング集中型のタスクの場合、CPU のパフォーマンスは継続的に消耗することしかできませんが、IO 集中型のタスクの場合、理想的にはこれは必要なく、処理のために IO デバイスのみに通知する必要があります。 . 、しばらくしてから戻ってデータを取得してください。 [関連チュートリアルの推奨事項: nodejs ビデオ チュートリアル 、プログラミング ビデオ ]
一部のシナリオでは、完了する必要がある無関係なタスクがいくつかあります。主流 2 つの方法があります:
- マルチスレッドの並列完了: マルチスレッドのコストは、スレッドの作成とスレッド コンテキストの切り替えの実行による高いオーバーヘッドです。さらに、複雑なビジネスでは、マルチスレッド プログラミングは、ロックや状態同期などの問題に直面することがよくあります。
- シングルスレッドの逐次実行: 表現は簡単ですが、逐次実行の欠点はパフォーマンスであり、少し遅いです。その結果、後続のコードは整理されました。
<p><code>node は、マルチスレッドのデッドロック、状態の同期などから遠ざけるために単一のスレッドを使用するという 2 つのコードの前に解決策を提供しました。問題; 非同期 IO を使用し、単一スレッドをブロックから遠ざけ、CPU をより効果的に使用できるようにします
Node が非同期を実装する方法
node
のマルチタスク ソリューションについてお話しましたが、これを node
の内部に実装するのは簡単ではありません。ここでは、誰もが実行できるように、オペレーティング システムの概念をいくつか紹介します。非同期実装とノードのイベント ループ メカニズムについては後ほど説明します:
ブロッキング IO とノンブロッキング IO
- IO のブロック: アプリケーション レベル IO 呼び出しを開始した後、データを待ち続けます。呼び出しは、オペレーティング システムのカーネル層がすべての操作を完了した後に終了します。
オペレーティング システム内のすべてのものはファイルです。ファイルは、カーネルが IO 操作を実行するときに、ファイル記述子を通じてファイルを管理します
- ノンブロッキング IO: 違いは次のとおりです。ファイル記述子が呼び出しの直後に返され、待機すると、CPU タイム スライスを他のトランザクションの処理に使用できるようになり、このファイル記述子を通じて結果を取得できるようになります。
いくつかの問題ノンブロッキングIOの場合:CPUの使用率は上がりますが、すぐにファイルディスクリプタが返されるため、いつIOが完了するか分かりませんステータスの変化を確認するにはポーリングを行うしかありません操作
さまざまなラウンド クエリ方法
-
read
: 最も原始的でパフォーマンスが最も低い方法で、次の手順で完全なデータの取得を完了します。 ##IO ステータスを繰り返し確認する
- select
:
ファイル記述子のイベント ステータス から判断すると、比較的消費量が少なくなりますが、欠点は次のとおりです。ストレージ ステータスに 1024 個の長さの配列を使用するため、最大 1024 個のファイル記述子を同時にチェックできます
- poll
:
select## の制限により#, poll
はリンクリストの保存に改良されています 他の方法も基本的に同じですが、ファイル記述子が多い場合、依然としてパフォーマンスが非常に低くなります
eopll- : このソリューションは、
linux
にあります。最も効率的な IO イベント通知メカニズムです。ポーリングに入るときに IO イベントがチェックされていない場合、起動するイベントが発生するまでスリープ状態になります。
#kqueue : と - epoll
同様ですが、FreeBSD システムの下でのみ存在します
epoll
はイベントを使用して CPU 消費量を削減しますが、CPU はスリープ中はほぼアイドル状態; 予期される非同期 IO は、アプリケーションによって開始されるノンブロッキング呼び出しである必要があります。トラバーサルやイベント ウェイクアップを介してポーリングする必要はありません。次のタスクは直接処理できます。必要なのは、 IO の完了後に、シグナルまたはコールバックを介してデータをアプリケーションに送信します。
#Linux には、シグナルまたはコールバックを通じてデータを送信する AIO メソッドもありますが、これは Linux でのみ利用可能であり、システム キャッシュを使用できない制限があります。
ノードでの非同期 IO の実装
結論からお話しますと、node非同期 IO の実装はマルチスレッドで実装されます。混乱を招く可能性があるのは、
node
は内部的にマルチスレッドですが、プログラマが開発した JavaScript
コードは単一のスレッドでのみ実行されることです。 <p><code>node
いくつかのスレッドを使用してブロッキング IO またはノンブロッキング IO とポーリング テクノロジを実行してデータ取得を完了し、1 つのスレッドに計算と処理を実行させ、スレッド間の通信を通じて IO から取得したデータを転送します。非同期IOのシミュレーションを簡単に実現します。
Linux ではすべてがファイルであり、ディスク、ハードウェア、ソケットなどのほぼすべてのコンピューター リソースが含まれるため、非同期 IO に加えて、コンピューター内の他のリソースも適用できます。ファイルが抽象化されているため、次のコンピュータ リソースへの呼び出しの紹介では、IO を例に取り上げます。
イベント ループ
プロセスが開始されると、 node
は while(true)
と同様のループを作成します。ループ本体が実行されると、 Tick
;
以下は、node
のイベント ループ フローチャートです。
#非常に単純な図と簡単な説明: 実行完了イベントが IO オブザーバーから取得されるたびに (これはリクエスト オブジェクトであり、簡単に理解すると、それにはリクエストで生成されたデータが含まれています)、はコールバック関数ではありません。引き続き次のイベント (リクエスト オブジェクト) を取り出し、コールバックがある場合はコールバック関数を実行します。
#非同期 IO の詳細
#注: プラットフォームが異なると、実装の詳細が異なります。この図では、関連するプラットフォームの互換性の詳細が隠されています。たとえば、Windows の IOCP で
PostQueuedCompletionStatus()
を使用して実行ステータスを送信します。 GetQueuedCompletionStatus
を通じて完了したリクエストを取得します。スレッド プールの詳細は IOCP の内部で実装されますが、Linux などのプラットフォームは
eopll を通じてこのプロセスを実装し、スレッド プールを自己実装します。 libuv
setTimtout
および
setInterval
IO などに加えて非同期呼び出しを必要とするコンピューター リソース、
node非同期 IO と関係のないものもいくつかあります
その他の非同期 API:
##setTimeout
#setInterval
setImmediate
process.nextTick
-
このセクションでは、最初に最初の 2 つの API について説明します
-
それらの実装原理は非同期 IO と似ていますが、
IO スレッド プールの参加を必要としないだけです :
setTimtout
and
setInterval作成されたタイマーはタイマー オブザーバー内の赤黒ツリーに挿入されます
毎回
check- が実行されると、赤黒ツリーから描画されます。タイマー オブジェクトを繰り返し実行し、タイマーが制限時間を超えているかどうかを確認します。
超えた場合は、イベント (リクエスト オブジェクト) をイベント キューを作成し、イベント ループでコールバック関数を実行します
- 赤黒ツリー: ここで簡単に説明すると、自己平衡化が可能な特殊なバランス バイナリ ツリーです。基本的にはバイナリ ツリーの深さです
-
O(l(log2 n)##この問題について検討しましたか? では、なぜタイマーにはスレッド プールの参加が必要ないのですか? 前の章で非同期 IO の実装原則を理解していれば、理解できると思います。記憶を深める理由を簡単に説明します: node
の IO スレッド プールは、IO を呼び出してデータが返されるのを待つ方法です (具体的な実装を参照)。これにより、JavaScript
の単一スレッドを有効にします。非同期 IO を呼び出し、IO 実行の完了を待つ必要がなく (IO スレッド プールが実行するため)、最終データを取得できます (オブザーバー モードを通じて: IO オブザーバーはスレッド プールから実行完了イベントを取得します) 、イベント ループ メカニズムが後続のコールバック関数を実行します)
#上記の段落は少し短いかもしれません。それでも理解できない場合は、前の図を見てください~
process .nextTick と
setImmediate
どちらの関数も関数の即時非同期実行を表すため、setTimeout を使用しない理由は次のとおりです。 (() => { . .. }, 0) で完了しますか?
タイマーの精度が十分ではありません- タイマーは赤黒ツリーを使用してタイマー オブジェクトと反復操作を作成するため、パフォーマンスが無駄になります
- つまり、
- process.nextTick
より軽量です
特に軽量: process.nextTick を呼び出すたびに、コールバック関数をキューに入れるだけです。次のラウンド
Tick になったら取り出して実行します。タイマーで赤黒ツリー方式を使用する場合
##O(log##2 #n) 、nextTick は
##O(1)##Thatprocess.nextTick## 違いは何ですか# と setImmediate の間?結局のところ、これらはすべてコールバック関数を非同期で即座に実行します。
process.nextTick のコールバック実行優先順位は setImmediate
よりも高くなります。
process.nextTick のコールバック関数は配列に格納され、イベント ループの各ラウンドで実行されます。- setImmediate
の結果はリンク リストに保存され、最初の
注: process.nextTick- のコールバック実行優先度が
setImmediate## のコールバック実行優先度よりも高い理由は、コールバックはサイクルの各ラウンドで順番に実行されます。 # イベント ループがオブザーバーを順番にチェックするためです。
process.nextTick は
idle オブザーバーに属し、
setImmediate は check
に属します。観察者。 iedlObserver> IO Observer> check Observer
高性能サーバー
ネットワークソケットの処理、
node は非同期 IO にも適用されます。ネットワーク ソケットでリッスンされるリクエストはイベントを形成し、IO オブザーバーに渡されます。イベント ループはこれらのネットワーク IO イベントを継続的に処理します。
JavaScrpt
にいる場合、対応するコールバック関数はレベルで渡され、これらのコールバック関数はイベント ループで実行されます (ネットワーク リクエストの処理)
一般的なサーバー モデル:
同期
プロセスごと --> リクエストごと #スレッドごと --> リクエストごと
#Andnode
イベント駆動型のアプローチが使用されますこれらのリクエストを処理するために、リクエストごとに追加の対応するスレッドを作成する必要はありません。スレッドの作成と破棄のオーバーヘッドを省略できます。同時に、スレッドの数が少ないため (## のみ)、オペレーティング システムのスケジューリング タスクが少なくなります。 #node 一部のスレッドは内部的に実装されています) コンテキスト切り替えのコストは非常に低いです。 -
古典的な問題--雪崩問題解決策:
問題の説明: サーバーが起動したばかりのときは、キャッシュにデータがありません。アクセス数が膨大な場合、同じ SQL
クエリが繰り返されるとデータベースに送信され、パフォーマンスに影響します。
解決策:
const proxy = new events.EventEmitter();
let status = "ready"; // 状态锁,避免反复查询
const select = function(callback) {
proxy.once("selected", callback); // 绑定一个只执行一次名为selected的事件
if(status === "ready") {
status = "pending";
// sql
db.select("SQL", (res) => {
proxy.emit("selected", res); // 触发事件,返回查询数据
status = "ready";
})
}
}
ログイン後にコピー
once
を使用して、要求されたすべてのコールバックをイベント キューにプッシュし、それを 1 回だけ実行した後にモニターを削除します。機能により、各コールバックが確実に実行されます。関数は一度だけ実行されます。同じ SQL ステートメントについては、同じクエリの最初から最後までに 1 回だけ実行されることが保証されます。同じ呼び出しの新しい到着は、データの準備ができるまでキューで待つだけで済み、結果がクエリされると、その結果をこれらの呼び出しで使用できるようになります。
プログラミング関連の知識について詳しくは、プログラミング教育をご覧ください。 !
以上がNode での非同期実装とイベント駆動について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。