私は、JavaScript でのファイル トラバーサル アルゴリズムを説明するこの要点を検討しています。 リーリー
私は、アルゴリズムのあらゆる隙間に async/await を詰め込みたいという衝動 (作者ではなく言語機能) に襲われます。 Node.js のアーキテクチャについてはよくわかりませんが、高度な非同期機能の背後には何らかの意図があるのではないでしょうか?これは非常に混乱を招き、主に宣言型手続き型 C/C スレッド/プロセス モデルに触れてきた私にとっては初めてのことです。私が知りたいのは、人によって意見があるかもしれないので、「モデルは何か」や「それが優れているのか」ではなく、「非同期性を推進するアイデアは何ですか?」です。これは、一般的なタスク ベースに対するブラウザーの応答性の必要性です。効率の問題?私の質問は、「なぜ非同期性がこれほど多く発生するのですか?」です。私はアドバイスを求めているのではなく、Node.js や JavaScript の歴史と進化を理解し、そのアーキテクチャを説明できる人を求めています。
重要なポイント: https://gist.github.com/lovasoa/8691344
P粉3289113082024-04-02 15:52:50
そうですね、大まかに言えば、その文の最初の部分とその文の後半は矛盾しています。サーバーの効率が低い場合、同時に到着する多数のクライアント要求に適切に応答できなくなります。したがって、サーバーの効率を高めて、クライアントの要求にできるだけ応答できるようにする必要があります。
ここで、いくつかの手順に戻って、示したコードで正確に何が起こっているのかを理解する必要があります。まず、言語は Javascript ですが、このコードの中核となるロジックと、async
、await
、およびジェネレーターの使用方法は、単に Javascript 言語によるものではありません。これは、JavaScript が実行される特定の環境 (この場合は、nodejs) が原因です。
この環境ではイベント ループを使用し、単一の Javascript スレッドを実行します。他のオペレーティング システム スレッドは、さまざまなシステムや一部のライブラリ実装に使用されますが、nodejs が Javascript を実行する場合、一度に 1 つの Javascript のみが実行されます (シングル スレッド)。
また、サーバーを設計するときは、大量の受信リクエストに応答できるようにする必要があります。 1 つのリクエストを処理し、他のすべてのリクエストが最初のリクエストが完了するまで待機してから次のリクエストを開始する必要はありません。ただし、nodejs イベント ループ モデルは複数のスレッドを使用しないため、複数のリクエスト ハンドラーを同時に直接実行しません。
nodejs によってデプロイされたソリューションは、さまざまなサーバー リクエスト ハンドラーの主なアクティビティおよびリクエストの処理にほとんどの時間が費やされるのは I/O (ネットワーク、ファイル I/O、データベースなど)/O であるという事実に基づいています。 )。これらは、独自の (JavaScript 以外の) 実装を持つ下位レベルの操作です。
したがって、すべての I/O 操作に対して非同期モデルをデプロイします。適切に作成されたサーバーは非同期 I/O 操作を開始でき、それが処理されている間 (Nodejs インタープリター自体で実行されているコード内ではなく)、インタープリターと NodeJS イベント ループは自由に他のことを行ったり、別のリクエストを処理したりできます。しばらくして、その非同期操作が完了すると、イベントがイベント ループに挿入され、インタープリターが実行中の操作を完了すると、非同期操作の結果を処理して操作を続行できます。
この方法では、JavaScript は 1 つのスレッドでのみ実行されますが、多くの受信リクエストを同時に「処理」できます。
はい、これは古い C/C スレッディング モデルとは完全に異なるモデルです。 Nodejs で効率的かつ効果的なサーバー コードを作成するためのこの異なるモデルを学ぶか、学ばないかのどちらかです。古いモデルを使い続けたい場合は、リクエスト ハンドラーをスレッド (Java、C など) で実行する別の環境を選択し、それを適切に実行することを目指します (関連する設計とテストのオーバーヘッドはもちろんあります)。すべてのマルチスレッド同時実行性について徹底的にテストされています)。
nodejs モデルの大きな利点の 1 つは、マルチスレッド実行モデルに存在する同時実行性の問題の多くの影響を受けにくいことです。 Nodejs モデルには、場合によっては回避策が必要となるいくつかの欠点もあります。たとえば、Javascript で書かれたリクエスト ハンドラーに CPU を集中的に使用するコードがある場合、これでも処理が行き詰まるため、CPU を集中的に使用するコードをメイン イベント ループから別のイベント ループに移動する方法を見つける必要があります。 thread 、または process (場合によってはワークキューも)。ただし、I/O はすべて非同期であるため、問題を引き起こすことなくメインスレッドに留まることができます。
別々の同時スレッドに必要なコードが少ないほど、発生する可能性が高い同時実行バグが減り、コードの完全なテストが容易になります。
さて、何かをループさせようとしているので、ループが必要になります。イベント ループをブロックしたくない場合、またはこれがタスクを完了する必要がある唯一のタイプの操作 (データベースでの検索の実行など) の場合、イベント ループで非同期操作を使用します。
非同期操作の使用は、何かを最適化することではありません。これは、適切なサーバー コードを記述し、イベント ループをブロックしないという中心的な設計に関するものです。そして実際、nodejs で使用可能なインターフェイス (データベース インターフェイスやネットワーク インターフェイスなど) は非同期インターフェイスのみを提供します。
この質問の仕方からすると、コアの Nodejs アーキテクチャをより深く理解し、イベント ループの仕組みと非同期 I/O 操作の仕組みについて詳しく読むことが有益であることがわかります。
まず、非同期 API (ネットワークやデータベースなど) を使用している場合は、選択肢がありません。この API を使用する非同期コードを設計します。非同期 API を使用するか同期 API を使用するかを選択できる場合 (Node.js のファイル システム アクセスの場合と同様)、API 呼び出しごとにイベント ループをブロックするかどうかを選択できます。イベント ループをブロックすると、サーバーのスケーラビリティと応答性に重大な悪影響を及ぼします。
この特定のコード例では、async
、await
、ジェネレーター、および yield
と同じ実装で非同期言語機能のキッチン シンクを使用しようとしています。通常はこれを行いません。この実装の要点は、次のように非常に簡単に使用できるインターフェイスを作成できることです。
リーリー
walk() は内部的に非同期です。この実装により、非同期実装の複雑さのほとんどすべてが API ユーザーから隠蔽され、API のコーディングが容易になります。単一の
await を適切な場所に配置することで、API のユーザーは、ほぼ同期しているかのようにコーディングできます。これらの Javascript 言語機能 (Promise、async、await、ジェネレーターなど) の目的は、非同期操作のコーディングを容易にすることです。
イベント ループ モデルの利点
プログラミングは簡単です。 すべての Javascript が同じスレッドで実行され、すべての共有データ アクセスが同じスレッドから行われるため、通常、スレッドから共有データにアクセスするときに同時実行性の問題に対処する必要はありません。共有データにアクセスするためにミューテックスは必要ありません。これらのミューテックスを使用すると、デッドロックのリスクに直面することはありません。
エラーが少なくなります。 スレッドから公開データにアクセスすることは、エラーのないコードを書くことよりはるかに困難です。完全に記述されていない場合、コードが競合状態に陥ったり、同時実行保護が欠如したりする可能性があります。さらに、これらの競合状態はテストが困難なことが多く、サーバーに大きな負荷がかかるまでは明らかにならない可能性があり、それでも再現するのは簡単ではありません。
スケーラビリティが向上します (場合によっては)。 主に I/O バウンドのコードの場合、協調的なイベント ループ モデルによりスケーラビリティが向上する可能性があります。これは、処理中の各リクエストによって個別のオペレーティング システム スレッドが発生せず、オーバーヘッドが追加されることがないためです。代わりに、通常は次のコールバックまたは Promise の待機に関連するクロージャ内に、アプリケーションの状態が少量しか存在しません。
イベント ループ プログラミングに関する記事
クールな子供たちがイベント ループを使用する理由 - これはまさに Java プログラミングでのイベント ループの使用に関するものですが、この議論はあらゆる環境に当てはまります