Node.jsは、シングルスレッド、非同期、ノンブロッキングI/Oであることを私は知っています。私はそれについてたくさん読みました。たとえば、PHPはリクエストごとに1つのスレッドを使用しますが、ノードはすべてに1つのスレッドのみを使用します。
3つのリクエストa、b、cがnode.jsサーバーに同時に到着するとします。これらのリクエストのうち3つは、大きなブロック操作を必要とします。たとえば、すべて同じ大きなファイルを読み取りたい場合です。
次に、要求はどのようにキューに入れられ、ブロッキング操作はどの順序で実行され、応答はどの順序でディスパッチされますか?もちろん、いくつのスレッドを使用しますか?
3つのリクエストについて、リクエストからレスポンスまでの流れを教えてください。
つのリクエストの一連のイベントの説明は次のとおりです:
したがって、実際に同時に実行される要求は1つだけですが、複数の要求が同時に「処理中」または「処理中」になる可能性があります。これは、システムがいつでもスレッドを自由に切り替えることができる複数のネイティブスレッドによる「プリエンプティブ」マルチタスクではなく、協調マルチタスクと呼ばれることもあります。JavaScriptの特定のスレッドは、システムに戻るまで実行され、その後、そしてその時だけ、Javascriptの別の部分が実行を開始できます。 Javascriptの一部は非ブロッキング非同期操作を開始できるため、Javascriptのスレッドは、非同期操作がまだ保留されている間にシステムに戻る(他のJavascriptを実行できるようにする)ことができます。これらの操作が完了すると、イベントキューにイベントがポストされ、他のJavascriptが完了してそのイベントがキューの一番上に来ると、そのイベントが実行されます。
シングルスレッド
ここで重要なのは、Javascriptの特定のスレッドは、システムに戻るまで実行されるということです。実行の過程で非同期操作(ファイルI/Oやネットワークなど)を開始した場合、それらのイベントが終了すると、イベントキューにイベントが配置され、JSエンジンが以前にイベントの実行を完了したときにそのイベントが処理され、コールバックが呼び出され、そのコールバックが実行されます。
このシングルスレッドの性質により、マルチスレッドモデルと比較して同時実行の処理方法が大幅に簡素化されます。すべてのリクエストが独自のスレッドを開始する完全なマルチスレッド環境では、共有を希望するすべてのデータは、単純な変数でも競合状態の影響を受け、誰もがそれを読み取る前にミューテックスで保護する必要があります。
Javascriptでは、複数のリクエストが同時に実行されないため、単純な共有変数アクセスにミューテックスは必要ありません。定義上、Javascriptの1つの部分が変数を読み取っている時点では、その時点で他のJavascriptは実行されていません(シングルスレッド)。
Node.jsはスレッドを使用します
注意すべき技術的な違いの1つは、Javascriptの実行のみがシングルスレッドであるということです。 node.jsの内部では、いくつかの目的でスレッド自体を使用します。たとえば、非同期ファイルI/Oは実際にはネイティブスレッドを使用します。ネットワークI/Oは実際にはスレッドを使用しません(ネイティブのイベント駆動型ネットワークを使用します)。
ただし、node.jsの内部でこのスレッドを使用しても、Javascriptの実行に直接影響することはありません。一度に実行されるJavascriptのスレッドはまだ1つだけです。
競合状態
非同期操作が開始されたときに変更されている状態の競合状態がまだ存在する可能性がありますが、これはマルチスレッド環境よりもはるかに一般的ではなく、これらのケースを識別して保護するのがはるかに簡単です。存在する可能性のある競合状態の例として、インターバルタイマーを使用して10秒ごとに複数の温度プローブから読み取りを行う単純なサーバーがあります。これらすべての温度測定値からデータを収集し、1時間ごとにそのデータをディスクに書き込みます。非同期I/Oを使用してデータをディスクに書き込みます。ただし、データをディスクに書き込むためにいくつかの異なる非同期ファイルI/O操作が使用されるため、これらの非同期ファイルI/O操作の間にインターバルタイマーが発生し、サーバーにあるデータが発生する可能性があります。変更するディスクへの書き込みの途中。これは悪いことであり、一貫性のないデータが書き込まれる可能性があります。単純な世界では、ディスクへの書き込みを開始する前にすべてのデータのコピーを作成することでこれを回避できるため、データのディスクへの書き込み中に新しい温度の読み取り値が入力されても、コピーは影響を受けず、コードは影響を受けません。一貫したデータのセットをディスクに書き込みます。ただし、このサーバーの場合、データが大きく、サーバー上のメモリが小さいため(Raspberry Piサーバーです)、すべてのデータのメモリ内コピーを作成することは現実的ではありません。
したがって、データがディスクに書き込まれているときにフラグを設定し、データがディスクに書き込まれているときにフラグをクリアすることで、問題が解決されます。このフラグが設定されているときにインターバルタイマーが起動すると、新しいデータは別のキューに入れられ、ディスクへの書き込み中のコアデータは変更されません。データのディスクへの書き込みが完了すると、キューがチェックされ、検出された温度データがメモリ内の温度データに追加されます。ディスクへの書き込みプロセスの整合性が維持されます。私のサーバーは、この「競合状態」が発生し、それが原因でデータがキューに入れられるたびに、イベントをログに記録します。そして、見よ、それはたまに起こり、データの整合性を維持するためのコードは機能します。