Node8 における AsyncHooks 非同期ライフサイクル

Node8 における AsyncHooks 非同期ライフサイクル

Async Hooks は Node8 の新機能です。NodeJs の非同期リソースのライフサイクルを追跡するための API をいくつか提供します。これは NodeJs の組み込みモジュールであり、直接参照できます。

定数 async_hooks = require('async_hooks');

これはめったに使用されないモジュールですが、なぜ存在するのでしょうか?

ご存知のとおり、JavaScript は最初からシングルスレッド言語として設計されています。これは、JavaScript の元々の設計意図に関係しています。元々の JavaScript は、ネットワーク速度が低かった時代に、ユーザーがサーバーの応答を待つ時間コストを削減するために、ページ上のフォーム検証を実行するためにのみ使用されていました。 Web フロントエンド技術の発展に伴い、フロントエンド機能はますます強力になり、ますます重要になっていますが、シングルスレッドで解決できない問題はないようです。比較すると、マルチスレッドはより複雑であるように思われるため、現在でもシングルスレッドが使用されています。

JavaScript はシングルスレッドなので、日常の開発ではタイマーや現在標準化されている Ajax など、時間のかかるタスクが常に存在します。これらの問題を解決するために、JavaScript は BOM、DOM、ECMAScript に分かれています。BOM は、非同期タスクと呼ばれる時間のかかるタスクを解決するのに役立ちます。

ブラウザの BOM は非同期タスクの処理に役立つため、ほとんどのプログラマーは非同期タスクの使用方法以外についてほとんど何も知りません。たとえば、同時にキューにある非同期タスクの数はいくつでしょうか?非同期処理が混雑しているかどうかなどの関連情報を直接取得する方法はありません。多くの場合、基盤となるレイヤーでは関連情報に注意を払う必要はありません。ただし、場合によっては関連情報が必要な場合に備えて、NodeJS では async_hooks という実験的な API が提供されています。なぜ NodeJS か? タイマーや http などの非同期モジュールを開発者が制御できるのは Node だけだからです。ブラウザーが対応する API を提供しない限り、ブラウザーの BOM は開発者によって制御されません。

async_hooksルール

async_hooks は、各関数がコンテキスト (非同期スコープと呼ばれる) を提供することを規定しています。各非同期スコープには、現在の非同期スコープのロゴである asyncId があります。同じ非同期スコープ内の asyncId は同じである必要があります。

複数の非同期タスクが並行して実行されている場合、asyncId を使用すると、どの非同期タスクを監視するかを区別できます。

asyncId は、自己増加する非反復の正の整数です。プログラムの最初の asyncId は 1 である必要があります。

簡単に言うと、async スコープは中断できない同期タスクです。中断できない限り、コードがどれだけ長くても asyncId を共有します。ただし、途中でコールバックや await など中断できる場合は、新しい非同期コンテキストが作成され、新しい asyncId が作成されます。

各非同期スコープには、現在の関数がその非同期スコープによってトリガーされることを示す triggerAsyncId があります。

asyncId と triggerAsyncId を使用すると、非同期呼び出しの関係とリンク全体を簡単に追跡できます。

async_hooks.executionAsyncId() は asyncId を取得するために使用されます。グローバル asyncId が 1 であることがわかります。

async_hooks.triggerAsyncId() は triggerAsyncId を取得するために使用され、その現在の値は 0 です。

定数 async_hooks = require('async_hooks');
console.log('asyncId:', async_hooks.executionAsyncId()); // asyncId: 1
console.log('triggerAsyncId:', async_hooks.triggerAsyncId()); // トリガーAsyncId: 0

ここでは、 fs.open を使用してファイルを開きます。 fs.open の asyncId は 7 で、 fs.open の triggerAsyncId は 1 になることがわかります。これは、 fs.open がグローバル呼び出しによってトリガーされ、グローバル asyncId が 1 であるためです。

定数 async_hooks = require('async_hooks');
console.log('asyncId:', async_hooks.executionAsyncId()); // asyncId: 1
console.log('triggerAsyncId:', async_hooks.triggerAsyncId()); // トリガーAsyncId: 0
定数 fs = require('fs');
fs.open('./test.js', 'r', (err, fd) => {
    console.log('fs.open.asyncId:', async_hooks.executionAsyncId()); // 7
    console.log('fs.open.triggerAsyncId:', async_hooks.triggerAsyncId()); // 1
});

非同期関数のライフサイクル

もちろん、実際のアプリケーションでは async_hooks はこのようには使用されません。正しい使用方法は、すべての非同期タスクが作成、実行、破棄される前、後、および後にコールバックをトリガーし、すべてのコールバックが asyncId で渡されることです。

async_hooks.createHook を使用して、非同期リソース フックを作成できます。このフックは、オブジェクトをパラメーターとして受け取り、非同期リソースのライフ サイクルで発生する可能性のあるイベントのコールバック関数を登録します。これらのフック関数は、非同期リソースが作成/実行/破棄されるたびにトリガーされます。

定数 async_hooks = require('async_hooks');

定数asyncHook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) { },
  破棄(非同期ID) { }
})

現在、createHook 関数は次の 5 種類のフック コールバックを受け入れることができます。

1. init(asyncId、type、triggerAsyncId、resource)

  • init コールバック関数は通常、非同期リソースが初期化されるときにトリガーされます。
  • asyncId: 各非同期リソースは一意の識別子を生成します
  • type: 非同期リソースのタイプ。通常はリソースのコンストラクターの名前です。

FSEVENTWRAP、FSREQCALLBACK、GETADDRINFOREQWRAP、GETNAMEINFOREQWRAP、HTTPINCOMINGMESSAGE、
HTTPCLIENTREQUEST、JSSTREAM、PIPECONNECTWRAP、PIPEWRAP、PROCESSWRAP、QUERYWRAP、
SHUTDOWNWRAP、SIGNALWRAP、STATWATCHER、TCPCONNECTWRAP、TCPSERVERWRAP、TCPWRAP、
TTYWRAP、UDPSENDWRAP、UDPWRAP、WRITEWRAP、ZLIB、SSLCONNECTION、PBKDF2REQUEST、
RANDOMBYTESREQUEST、TLSWRAP、マイクロタスク、タイムアウト、即時、TickObject

  • triggerAsyncId: 現在の非同期リソースの作成をトリガーする対応する非同期スコープの asyncId を示します。
  • リソース: 初期化される非同期リソースオブジェクトを表します

async_hooks.createHook 関数を使用して、各非同期リソースのライフサイクルで発生する init/before/after/destory/promiseResolve やその他の関連イベントのリスナー関数を登録できます。
同じ非同期スコープが複数回呼び出されて実行されることがあります。実行回数に関係なく、asyncId は同じである必要があります。監視機能を使用すると、実行回数と実行時間、オンライン コンテキストとの関係を簡単に追跡できます。

2. 前(非同期ID)

before 関数は、通常、asyncId に対応する非同期リソース操作が完了した後、コールバックが実行される前に呼び出されます。before コールバック関数は、コールバックされる回数によって複数回実行される場合があります。使用時にはこの点に注意してください。

3.after(非同期ID)

after コールバック関数は通常、非同期リソースがコールバック関数を実行した直後に呼び出されます。コールバック関数の実行中にキャッチされない例外が発生した場合は、「uncaughtException」イベントがトリガーされた後に after イベントが呼び出されます。

4.破棄(非同期ID)

asyncId に対応する非同期リソースが破棄されたときに呼び出されます。一部の非同期リソースの破棄はガベージ コレクション メカニズムに依存するため、メモリ リークが原因で、破棄イベントがトリガーされない場合があります。

5. promiseResolve(非同期ID)

Promise コンストラクターの resolve 関数が実行されると、promiseResolve イベントがトリガーされます。場合によっては、一部の解決関数が暗黙的に実行されます。たとえば、.then 関数は新しい Promise を返し、この時点でこれも呼び出されます。

定数 async_hooks = require('async_hooks');

// 現在の実行コンテキストの asyncId を取得します
eid は async_hooks.executionAsyncId() です。

// 現在の関数をトリガーする asyncId を取得します
定数 tid = async_hooks.triggerAsyncId();

// 新しい AsyncHook インスタンスを作成します。これらのコールバックはすべてオプションです。const asyncHook =
    async_hooks.createHook({ init、before、after、destroy、promiseResolve });

//AsyncHook.enable() を実行するには宣言する必要があります。

// 新しい非同期イベントのリッスンを無効にします。
asyncHook を無効にします。

関数 init(asyncId, type, triggerAsyncId, resource) { }

関数before(asyncId) { }

関数 after(asyncId) { }

関数destroy(asyncId) { }

関数promiseResolve(asyncId) { }

約束

Promise は特別なケースです。十分に注意すれば、init メソッドの型には PROMISE がないことがわかります。 Promise の asyncId を取得するために ah.executionAsyncId() のみを使用する場合、正しい ID を取得できません。実際のフックを追加した後にのみ、async_hooks は Promise コールバックの asyncId を作成します。

つまり、V8 では asyncId を取得するための実行コストが高いため、デフォルトでは Promise に新しい asyncId を割り当てません。
つまり、デフォルトでは、promise または async/await を使用すると、現在のコンテキストの正しい asyncId と triggerId を取得できません。しかし、それは問題ではありません。async_hooks.createHook(callbacks).enable() 関数を実行することで、asyncId を Promise に強制的に割り当てることができます。

定数 async_hooks = require('async_hooks');

定数asyncHook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId, resource) { },
  破棄(非同期ID) { }
})
asyncHook を有効にします。

Promise.resolve(123).then(() => {
  console.log(`asyncId ${async_hooks.executionAsyncId()} トリガーId ${async_hooks.triggerAsyncId()}`);
});

また、Promise は init および promiseResolve フックイベント関数のみをトリガーし、before イベントと after イベントのフック関数は Promise がチェーンされたときのみ、つまり .then/.catch 関数内で Promise が生成された場合にのみトリガーされます。

新しいPromise(resolve => {
    解決する(123);
})。次に、データ => {
    コンソールにログ出力します。
})

上記には 2 つの Promise があり、最初の Promise は新しいインスタンス化によって作成され、2 番目の Promise はその後に作成されることがわかります (わからない場合は、以前の Promise ソース コードの記事を参照してください)。

ここでの順序は、新しい Promise を実行するときに、独自の init 関数が呼び出され、解決時に promiseResolve 関数が呼び出されます。次に、then メソッドで 2 番目の Promise の init 関数を実行し、次に 2 番目の Promise の before、promiseResovle、および after 関数を実行します。

例外処理

登録された async-hook コールバック関数で例外が発生した場合、サービスはエラー ログを出力し、直ちに終了します。同時に、すべてのリスナーが削除され、プログラムを終了させるための 'exit' イベントがトリガーされます。

プロセスがすぐに終了する理由は、これらの async-hook 関数が不安定に実行されると、次に同じイベントがトリガーされたときに例外がスローされる可能性が高いためです。これらの関数は主に非同期イベントを監視するために使用されます。不安定な場合は、適時に検出して修正する必要があります。

非同期フックコールバックでログを出力する

console.log 関数も非同期呼び出しであるため、async-hook 関数で console.log を再度呼び出すと、対応するフック イベントが再度トリガーされ、無限ループ呼び出しが発生します。したがって、async-hook 関数で追跡するには、同期ログを使用する必要があります。fs.writeSync 関数を使用できます。

定数 fs = require('fs');
'util' が必要です。

関数debug(...args) {
  fs.writeFileSync('log.out', `${util.format(...args)}\n`, { フラグ: 'a' });
}

[参考資料 - AsyncHooks] (https://nodejs.org/dist/latest-v15.x/docs/api/async_hooks.html)

Node8 の AsyncHooks の非同期ライフサイクルに関するこの記事はこれで終わりです。Node AsyncHooks の非同期ライフサイクルに関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後も 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Node.js http モジュールの使用
  • Nodejs 探索: シングルスレッドの高並行性の原理を深く理解する
  • Node.jsを理解するのはとても簡単です
  • node.js グローバル変数の具体的な使用法
  • Nodejs エラー処理プロセス記録
  • Expressを使用してプロジェクトを自動的にビルドするNode.jsのプロセス全体
  • ノードでシェルスクリプトを使用する方法
  • Node.js の TCP 接続処理のコア プロセス
  • Nodejs 配列キューと forEach アプリケーションの詳細な説明
  • Node.jsとDenoの比較

<<:  Alibaba Cloud Centos7のインストールとSVNの設定

>>:  Centos7.3 での mysql5.7 のインストールと設定のチュートリアル

推薦する

Dockerイメージのサイズを縮小する6つの方法

2017 年に Vulhub に取り組み始めてから、私は厄介な問題に悩まされてきました。Docker...

Vue パッケージ化後の空白ページの解決策

1. vue-cli がプロジェクト パッケージを作成した後にページが空白になる問題の解決方法コマン...

Dockerはローカルイメージをパッケージ化し、他のマシンに復元します

1. docker imagesを使用して、このマシン上のすべてのイメージファイルを表示します。 2...

MySQL で不明なフィールド名を回避する方法

序文この記事では、DDCTF の 5 番目の質問、つまり不明なフィールド名をバイパスする手法を紹介し...

PostgreSQL正規表現の一般的な機能の概要

PostgreSQL正規表現の一般的な機能の概要正規表現は、複雑なデータ処理を必要とするプログラムに...

制限を使用すると、MySQL のページングがどんどん遅くなるのはなぜですか?

目次1. テスト実験2. 制限ページング問題に対するパフォーマンス最適化手法2.1 テーブルをカバー...

MySQL ストアド プロシージャで case ステートメントを使用する詳細な例

この記事では、例を使用して、MySQL ストアド プロシージャでの case ステートメントの使用方...

LinuxはRsync+Inotifyを使用してローカルとリモートのデータのリアルタイム同期を実現します。

0x0 テスト環境本社本番サーバーと支社バックアップサーバーはリモートデータバックアップが必要です...

Linux lseek関数の使い方の詳しい説明

注:記事に誤りがある場合は、メッセージを残して指摘してください。ご協力ありがとうございます。名前名前...

Dockerコンテナにnginxを簡単にデプロイするプロセスの分析

1. コンテナにnginxサービスをデプロイするcentos:7 イメージはコンテナを実行し、このコ...

JVM 上の高性能データ形式ライブラリ パッケージである Apache Arrow の紹介とアーキテクチャ (Gkatziouras)

Apache Arrow は、BigQuery を含むさまざまなビッグデータ ツールで使用される一...

Html+CSS 描画三角形アイコン

まずはレンダリングを見てみましょう: XML/HTML コードコンテンツをクリップボードにコピー&l...

ico ミラー コードを HTML に追加します (favicon.ico はルート ディレクトリに配置されます)

コード:コードをコピーコードは次のとおりです。 <!DOCTYPE html PUBLIC &...

複数のパッケージソースから同時にパッケージをロードするようにnpmを設定する方法

目次1. ローカルストレージを構築する2. npmパッケージを作成し、プライベートリポジトリにアップ...

jQuery Ajax チャットボットの実装事例

チャットボットは多くの手作業を省くことができ、顧客サービス、天気予報対応など、さまざまな状況で使用で...