Node.js は、同時実行性の高いネットワーク アプリケーション サービスを構築するためのツールボックスのメンバーになったのはなぜですか?この記事では、プロセス、スレッド、コルーチン、I/O モデルの基本概念から始めて、Node.js と同時実行モデルについて包括的に紹介します。
一般に、プログラムの実行インスタンスをプロセスと呼びます。これは、オペレーティング システムによるリソースの割り当てとスケジューリングの基本単位です。通常、次の部分が含まれます。
进程表
テーブルと呼ばれ、各プロセスは进程表项
(进程控制块
とも呼ばれます) を占有します。このエントリには、プログラム カウンター、スタック ポインタ、メモリ割り当て、オープン ファイルのステータス、スケジュール情報などの重要なプロセス ステータスが含まれます。 . プロセスが一時停止された後、オペレーティング システムがプロセスを正しく復活できることを確認するための情報。プロセスには次のような特徴があります。
プログラムが 2 回実行される場合、オペレーティング システムでコードの共有が可能であっても (つまり、コードのコピーが 1 つだけメモリ内にある)、実行中のプログラムの 2 つのインスタンスが異なるものであることは変わらないことに注意してください。事実を処理します。
プロセスの実行中、中断や CPU スケジューリングなどのさまざまな理由により、プロセスは次の状態の間で切り替わります。
のみであることがわかります。
場合によっては、次の問題を解決するためにスレッドを使用する必要があります。
スレッドについては、次の点を理解する必要があります。
スレッドの基本的な特性を理解したところで、いくつかの一般的なスレッドのタイプについて説明しましょう。
カーネル状態スレッドは、オペレーティング システムによって直接サポートされるスレッドです。その主な機能は次のとおりです。
主な特徴は次のとおりです。
軽量プロセス (LWP) は、カーネル上に構築され、サポートされるユーザー スレッドです。その主な機能は次のとおりです。
ユーザー空間は、軽量プロセス (LWP) を通じてのみカーネル スレッドを使用できます。したがって、カーネル スレッドをサポートすることによってのみ、軽量
プロセス (LWP) のほとんどの操作でシステム コールを開始することができます。比較的高価です (ユーザー モードとカーネル モードの切り替えが必要)。
各軽量プロセス (LWP) は特定のカーネル スレッドに関連付けられる必要があるため、
すべての共有アドレス スペースとシステム リソースにアクセスできます。
上記では、一般的なスレッドの種類 (カーネル状態スレッド、ユーザー状態スレッド、軽量プロセス) を簡単に紹介しました。実際の使用では、それぞれの用途に応じて自由に使用できます。一般的な 1 対 1、多対 1、多対多、その他のモデルなどの組み合わせについては、紙面の都合上、この記事では詳しく説明しませんので、興味のある方はご自身で学習してください。
(Fiber とも呼ばれます) は、開発者が実行スケジューリング、状態維持、その他の動作を自分で管理できるようにする、スレッド上に構築されたプログラム実行メカニズムです。その主な特徴は次のとおりです
JavaScript でよく使用するasync/await
は、次の例のようなコルーチンの実装です。
function updateUserName(id, name) { const user = getUserById(id); user.updateName(名前); true を返します。 } 非同期関数 updateUserNameAsync(id, name) { const user = await getUserById(id); user.updateName(name) を待ちます。 true を返します。
上記の例では、関数updateUserName
およびupdateUserNameAsync
内の論理的な実行シーケンスは、
関数
getUserById
呼び出し、その戻り値を変数user
に割り当て、user
のupdateName
メソッドを呼び出し、true
を返します。2 つの主な違いは、実際の操作中の状態制御にあります。
updateUserName
の実行中は、上記の論理シーケンスに従って順番に実行され、updateUserNameAsync
の実行中にも、次の論理シーケンスに従って順番に実行されます。上記の論理シーケンスで実行されますが、 await
発生すると、 updateUserNameAsync
一時停止され、一時停止された場所に現在のプログラム状態が保存されます。 await
後のプログラム フラグメントが戻って一時停止前のプログラム状態に復元されるまで、 updateUserNameAsync
は再度起動されません。次のプログラムに進みます。上記の分析から、大胆に推測できます。コルーチンが解決する必要があるのは、プロセスやスレッドが解決する必要があるプログラムの同時実行性の問題ではなく、非同期タスク (ファイル操作、ネットワーク リクエストなど) を処理するときに遭遇する問題です。 async/await
登場する前は、非同期タスクはコールバック関数を介してしか処理できませんでしたが、これにより簡単に回调地狱
に陥り、一般に保守が困難な混乱したコードが生成される可能性がありました。コルーチンを介して非同期コードの同期を実現できます。 。
心に留めておく必要があるのは、コルーチンの中核機能は、特定のプログラムを一時停止してプログラムの一時停止位置の状態を維持し、将来のある時点で一時停止位置からプログラムを再開し、継続的に実行できることであるということです。一時停止位置のプログラムの後に次のセグメントを実行します。
完全なI/O
操作は次の段階を経る必要があります。
I/O
操作リクエストを開始し、I/O
操作リクエストを処理します。準備段階と実際の実行段階に分けて処理し、処理結果をユーザースレッドに返します。I/O
操作は、阻塞I/O
、非阻塞I/O
、同步I/O
、および异步I/O
これらのタイプについて説明する前に、まず次の 2 つのセットについて理解します。概念 (ここでは、サービス A がサービス B を呼び出すと仮定します):
阻塞/非阻塞
:
阻塞调用
非阻塞调用
になります。同步/异步
:
同步
です。A回调
を通じて実行します。結果は A に通知され、その後サービス B が异步
なります。多くの人が阻塞/非阻塞
と同步/异步
を混同することが多いため、特別な注意を払う必要があります。
阻塞/非阻塞
サービスの调用者
に対するものであり、同步/异步
サービスの被调用者
に対するものです。阻塞/非阻塞
と同步/异步
を理解した後、具体的なI/O 模型
を見てみましょう。
定義: ユーザー スレッドがI/O
システム コールを開始すると、 I/O
操作全体が処理され、結果がユーザー スレッドに返されるまで、ユーザー スレッドは直ちに阻塞
ます。 (スレッド) プロセスは、阻塞
状態を解放し、後続の操作を続行できます。
特徴:
I/O
操作を実行するときに CPU リソースを占有しませんI/O
リクエストが受信 (スレッド) スレッドをブロックする可能性があるため、 I/O
に時間内に応答するには、各リクエストに受信 (スレッド) スレッドを割り当てる必要があり、これにより膨大なリソースが発生します。使用法、および長い接続リクエストの場合、受信 (スレッド) リソースを長時間解放できないため、将来新しいリクエストがあった場合、深刻なパフォーマンスのボトルネックが発生します。定義:
I/O
システム コールを開始した後、 I/O
操作の準備ができていない場合、 I/O
コールはエラーを返し、ユーザーはスレッド (スレッド) に入る必要がありますが、ポーリングを使用してI/O
操作の準備ができているかどうかを検出します。I/O
操作はユーザーのスレッドをブロックします。ユーザーのスレッド。特徴:
I/O
操作の準備が完了する前にI/O
while
CPU を占有して CPU リソースを消費する必要があります。I/O
操作の準備が完了すると、その後の実際のI/O
操作はユーザーのスレッド (スレッド) への入力をブロックします。ユーザー プロセス (スレッド) がI/O
システム コールを開始した後、 I/O
コールによってユーザー プロセス (スレッド) がブロックされる場合、そのI/O
コールは同步I/O
です。同步I/O
、それ以外の場合は异步I/O
です。
I/O
操作同步
か异步
かを判断する基準は、ユーザー スレッドとI/O
操作の間の通信メカニズムです。
同步
の場合、ユーザー スレッドとI/O
間の対話はカーネル バッファーを通じて同期されます。つまり、カーネルはI/O
操作の実行結果をバッファに同期し、バッファ内のデータをユーザー スレッドにコピーします。このプロセスは、 I/O
操作が完了するまでユーザー スレッドをブロックします异步
場合によっては、ユーザー スレッド (スレッド) とI/O
間の対話はカーネルを通じて直接同期されます。つまり、カーネルはI/O
操作の実行結果をユーザー スレッド (スレッド) に直接コピーします。 not ユーザー (スレッド) プロセスをブロックします。Node.js は、シングル スレッドのイベント駆動型の非同期I/O
モデルを使用します。個人的に、このモデルを選択した理由は次のとおりだと思います。JavaScript
I/O
大量に消費します。高い同時実行性を確保しながらマルチスレッド リソースを合理的かつ効率的に管理する方法は、単一スレッド リソースの管理よりも複雑です。つまり、簡素化と効率化を目的として、Node.js はシングルスレッドのイベント駆動型非同期I/O
モデルを採用し、メイン スレッドと補助ワーカー スレッドの EventLoop を通じてそのモデルを実装します (
Node.js は、CPU を大量に使用する (つまり、多くの計算を必要とする) タスクの実行には適していないことに注意してください。これは、EventLoop と JavaScript コード (非同期イベント タスク コード) が同じスレッドで実行されるためです (つまり、実行時間が長すぎると、アプリケーションに長時間実行を必要とするタスクが多数含まれていると、メイン スレッドがブロックされる可能性があり、サーバーのスループットが低下し、場合によってはその可能性もあります。サーバーが応答しなくなる原因となります。
Node.js は、フロントエンド開発者が現在も将来も直面しなければならないテクノロジーですが、ほとんどのフロントエンド開発者は、Node の同時実行モデルをよりよく理解できるようにするために、Node.js について表面的な知識しか持っていません。 .js、この記事では、最初にプロセス、スレッド、コルーチンを紹介し、次にさまざまなI/O
モデルを紹介し、最後に Node.js の同時実行モデルについて簡単に説明します。 Node.js 同時実行モデルを紹介するスペースはあまりありませんが、関連する基本をマスターし、Node.js の設計と実装を深く理解することによって、それが 2 倍の成果を得ることができると著者は考えています。半分の労力で。
最後に、この記事に間違いがあれば修正していただければ幸いです。皆さんが毎日楽しくコーディングできることを願っています。