blog

非同期IO

ノード指向のネットワーク設計は、並行性をもたらす標準的なものです。具体的には、ユーザーエクスペリエンスとリソース割り当てから次のように展開します。 1. ユーザーエクスペリエンス フロントエンド ブラ...

Jul 6, 2020 · 4 min. read
シェア

非同期IO

ノードベース = 非同期IO + シングルスレッド + イベント駆動

.なぜ非同期IOなのか

Nodeはウェブ用に設計されており、同時並行処理を標準としています。具体的には、ユーザーエクスペリエンスとリソース割り当てから展開されます:

ユーザー体験

フロントエンドブラウザのJS実行はUIレンダリングとスレッドを共有しているため、JSのブロックがUIレンダリングのブロックを引き起こす可能性があります。

フロントエンドではこの問題を非同期リクエストで解決します。

バックエンドでは、データ分散が一般的になっているため、非同期IOはリソース要求の速度を効果的に向上させることができ、その結果、フロントエンドの要求により速く応答し、ユーザーエクスペリエンスを向上させることができます。

リソースの割り当て

  • マルチスレッドはCPU使用率を向上させますが、デッドロック状態の同期スレッド生成やスイッチングオーバーヘッドなどの問題を引き起こす可能性があります。
  • 同期IOはハードウェアリソースをブロックします。

以上の2点から、シングルスレッド非同期IOが最適解となります。

.非同期IO実装の現状

非同期IOとノンブロッキングIOの比較

非同期IOはノンブロッキングIOと同等に見えるかもしれませんが、コンピュータシステムのカーネルから見ると、ブロッキング/ノンブロッキングIOだけが認識され、アプリケーションで利用可能になります。

  • ブロッキングIOは、++呼び出しが完了する前に、++すべての操作がカーネルレベルで完了する必要があるという事実によって特徴付けられます。

  • ノンブロッキングIOの特徴は、++データなしで直接返すことですが、データを取得するには、++再度ポーリングする必要があります。

理想的なノンブロッキング非同期IO

...

現実的な非同期IO

望ましい非同期IOを実現するには、スレッドプールを使って非同期IOをシミュレートする必要があります。

.Node非同期IO

ノードの非同期IO実装 = イベントループ + オブザーバ + リクエストオブジェクト

イベントループ

Node プロセスがオープンされると、while(true)のようなループを実行します:

  • ノード・プロセスが開始し、処理すべきイベントがあるかどうかをチェックします。
    • イベントがある:イベントを取り出し、それに関連するコールバックがあるかどうかをチェックします。
      • コールバックあり:コールバックを実行
      • コールバックなし:現在のループの終了、次のループの開始
    • イベントなし:ノードプロセス終了

オブザーバ

  • Nodeプロセスは、オブザーバーが処理すべきイベントがあるかどうかを判断します。Tickの各ラウンドで、Nodeはオブザーバーに保留中のイベントがあるかどうかを尋ねます。
  • オブザーバーは一人ではありません。
  • オブザーバーは複数のイベントに対応でき、オブザーバーはイベントを分類します。

リクエストオブジェクト

リクエスト・オブジェクトは、JSによる非同期IO呼び出しの実行からコールバック関数の実行までのプロセスの重要な中間体です。

非同期IOの最初のフェーズ

  • 非同期IOコールの開始
    • JSfsコアモジュールの呼び出し
    • コアモジュールから C++ 組み込みモジュールへの呼び出し
    • libuvプラットフォームの判断
    • uvの呼び出し_fs_open()
  • uv_fs_open 関数でのリクエストオブジェクトのラップ

    JS 呼び出し時に渡されたパラメータとコールバックは、リクエストオブジェクト FSReqWrap のプロパティとして追加されます。
  • リクエストオブジェクトをスレッドプールにプッシュ

実行コールバック

非同期IOの第2フェーズ

  • IO操作が実行された後、結果はリクエストオブジェクトのresultプロパティに追加され、IOオブザーバに投げられます。
  • イベントループは、IOオブザーバを通してリクエストオブジェクトを取得し、イベントとして処理します。

まとめ

.非IO非同期API

タイマー

JSによって呼び出されたタイマーは、タイマーオブザーバの赤黒ツリーに挿入され、Tickが時間を超過するかどうかを毎回チェックし、超過した場合はイベントが形成され、コールバック関数が即座に実行されます。

process.nextTick()

process.nextTick()は、タイマー・オブジェクトを作成するのに比べて軽量です。

setImmediate()

setImmediate()の効果はprocess.nextTick()と同じに見えます。違いは

  • 異なる優先順位:setImmediate()はcheckオブザーバに属し、process.nextTick()はidleオブザーバに属し、イベントループは2種類のオブザーバのチェック優先順位が異なります:++idleオブザーバ>IOオブザーバ>checkオブザーバ++。
  • process.nextTick()はコールバックを配列に保存し、setImmediate()はコールバックを連結テーブルに保存します。各ティックで、配列内のすべてのコールバックが実行されますが、連結リスト内のコールバックはラウンドごとに1つだけ実行されます。
// つのnextTick()コールバック関数を追加する
process.nextTick(function () {
 console.log('nextTick遅延実行1');
});
process.nextTick(function () {
 console.log('nextTick遅延実行2');
});
// 2つのsetImmediate()コールバック関数を追加する
setImmediate(function () {
 console.log('setImmediate遅延実行1');
 // 次のループに進む
 process.nextTick(function () {
 console.log('強い挿入');
 });
});
setImmediate(function () {
 console.log('setImmediate遅延実行2');
});
console.log('通常の実行');
通常の実行
nextTick遅延実行1
nextTick遅延実行2
setImmediate遅延実行1
イベントループの強力な挿入
setImmediate遅延実行2

V.イベント・ドリブンと高性能サーバー

イベント・ドリブンの本質は、メイン・ループとイベントのトリガー、そして

IOオブザーバ用イベントコールバック、了解。

.

イベントループは非同期実装の中心であり、ブラウザ側の実行モデルとほぼ一致しています。

Read next

ブラウザアーキテクチャの進化

同じプロセス内のスレッドは、プロセスに割り当てられたメモリリソースを共有できます。プロセス間通信はIPCで実現されます。基本的な事実:CPUが速すぎてレジスタしか追いつかず、RAMやバスにぶら下がっている他のデバイスは完全に手が届きません。 必ず知っておくべき事実:実行...

Jul 6, 2020 · 12 min read