NodejsのBlocking vs Non-Blockingについて

ブロッキング

ブロッキングとは、Node.jsプロセス内で追加のJavaScriptの実行が、非JavaScript操作が完了するまで待機する必要がある場合を指します。これは、ブロッキング操作が発生している間にイベントループがJavaScriptの実行を継続できないためです。

Node.jsでは、I/OなどのJavaScript以外の操作を待つ場合は、ブロッキング処理が発生することになります。つまり、イベントループがI/O操作が完了するまで待機するため、その間に他のJavaScriptコードを実行することができます。しかし、パフォーマンスが低下する原因が、JavaScriptの処理自体にある場合は、ブロッキングとは呼ばれません。この場合、JavaScriptの処理に必要なCPUリソースが多く消費されるため、プログラムのパフォーマンスが低下します。

Node.js標準ライブラリで、libuvを使用する同期的なメソッドが最も一般的なブロッキング操作として使用されます。ネイティブモジュールにもブロッキングメソッドがある場合があります。

Node.js標準ライブラリのすべてのI/Oメソッドには、コールバック関数を受け入れる非同期バージョンがあり、これらは非ブロッキングです。一部のメソッドには、名前がSyncで終わるブロッキングの対応方法もあります。

Syncで終わるブロッキングの対応方法について
Node.jsの標準ライブラリには、非同期処理でI/O操作を実行するためのメソッドが用意されています。これらのメソッドは、I/O操作が完了した時点でコールバック関数を呼び出すことによって、非同期処理を実現しています。このため、このようなメソッドは非ブロッキング処理として扱われます。

一方、同期処理でI/O操作を実行するためのメソッドも用意されています。これらのメソッドは、名前の末尾にSyncが付いています。これらのメソッドを使用する場合、I/O操作が完了するまで処理がブロックされるため、プログラムのパフォーマンスが低下する可能性があります。そのため、これらのメソッドは、非同期処理を使用できる場合には非同期処理を使用することが推奨されています。

例えば、ファイルを読み込むための非同期処理メソッドは、fs.readFile()です。一方、同期処理メソッドはfs.readFileSync()です。fs.readFileSync()を使用する場合、I/O操作が完了するまで処理がブロックされるため、ファイルの読み込みに時間がかかる場合には、プログラムのパフォーマンスが低下します。そのため、ファイルの読み込みが非同期処理であるfs.readFile()を使用することが推奨されます。


同時実行性とスループット

Node.jsにおけるJavaScriptの実行は、シングルスレッドで行われるため、並行性とは、他の作業を完了した後にJavaScriptのコールバック関数を実行するイベントループの能力を指します。並行的に実行することが期待されるコードは、I/Oなどの非JavaScript操作が発生している間もイベントループを継続して実行できるようにする必要があります。

たとえば、Webサーバーへの各リクエストが完了するのに50msかかり、そのうち45msが非同期で処理できるデータベースI/Oである場合を考えてみましょう。ブロッキングするメソッドではなく、ノンブロッキングな非同期の操作を選択することで、その45msを他のリクエストの処理に使うことができます。これは、ノンブロッキングなメソッドを使用することで、容量に大きな違いが生まれることを示しています。

イベントループは、他の多くの言語のモデルと異なり、並行的な作業を処理するために追加のスレッドを作成する必要がないことに注意してください。

ブロッキング コードとノンブロッキング コードを混在させる危険性

I/O (Input/Output)を扱う際に避けるべきパターンがあります。以下に例を示します。

const fs = require("fs");
fs.readFile("/file.md", (err, data) => {
  if (err) throw err;
  console.log(data);
});
fs.unlinkSync("/file.md");

上記の例では、fs.unlinkSync()がfs.readFile()よりも先に実行される可能性があり、実際にファイルが読み込まれる前にfile.mdが削除される可能性があります。 完全に非ブロッキングで、正しい順序で実行されることが保証された書き方は次のとおりです。


const fs = require("fs");
fs.readFile("/file.md", (readFileErr, data) => {
  if (readFileErr) throw readFileErr;
  console.log(data);
  fs.unlink("/file.md", (unlinkErr) => {
    if (unlinkErr) throw unlinkErr;
  });
});

上記のコードは、fs.readFile()のコールバック内でfs.unlink()を非ブロッキングで呼び出し、操作の正しい順序を保証します。つまり、fs.readFile()が完了してから、fs.unlink()が実行されることが保証されます。

libuvについて

libuvは、Node.jsが非同期I/O操作を実現するために使用するマルチプラットフォームのCライブラリです。Node.jsでは、JavaScriptコードが実行されるスレッド(メインスレッド)は単一であるため、libuvが提供するスレッドプールを使用して、非同期I/O操作を実行することができます。これにより、JavaScriptの実行がブロックされず、イベントループが中断されることがなくなります。

libuvは、ネットワーク、ファイルシステム、ユーザー入力、タイマーなどのI/O操作に対応しています。また、タスクのスケジューリングやプロセスのスピンロックなど、Node.jsが実行されるプラットフォームに依存する機能も提供しています。

libuvは、Node.jsの非同期I/Oモデルの中心的な役割を果たしており、Node.jsが高いパフォーマンスとスケーラビリティを実現するために必要不可欠なライブラリです。


この記事が気に入ったらサポートをしてみませんか?