1.なぜJavaScriptはシングルスレッドされているのですか?
JavaScript言語の主な機能は単一のスレッドです。つまり、同時に1つのことしかできないことを意味します。それでは、なぜJavaScriptに複数のスレッドがあるのでしょうか?これにより、効率が向上します。
JavaScriptの単一スレッドは、その目的に関連しています。ブラウザのスクリプト言語として、JavaScriptの主な目的は、ユーザーと対話してDOMを操作することです。これにより、単一のスレッドのみができると判断され、それ以外の場合は非常に複雑な同期の問題を引き起こします。たとえば、JavaScriptが同時に2つのスレッドがあると仮定します。1つのスレッドは特定のDOMノードにコンテンツを追加し、もう1つのスレッドはこのノードを削除します。
したがって、複雑さを避けるために、JavaScriptはその出生からの単一のスレッドであり、この言語の中核的な特徴となり、将来は変わりません。
マルチコアCPUのコンピューティングパワーを利用するために、HTML5はWebワーカー標準を提案し、JavaScriptスクリプトが複数のスレッドを作成できるようにしますが、子スレッドはメインスレッドによって完全に制御され、DOMを操作できません。したがって、この新しい標準は、JavaScriptの単一スレッドの性質を変えません。
2。タスクキュー
単一のスレッドは、すべてのタスクをキューに掲載する必要があり、次のタスクが実行される前に以前のタスクが実行されることを意味します。前のタスクに長い時間がかかる場合、次のタスクが待たなければなりません。
キューがコンピューティングの大量であり、CPUが忙しすぎる場合、それは問題ありませんが、IOデバイス(入力と出力デバイス)が非常に遅いため、CPUは多くの場合アイドル状態です(Ajax操作はネットワークからデータを読み取るなど)、結果が出てから出てくる前に出てくるのを待つ必要があります。
JavaScript言語のデザイナーは、現時点では、CPUがIOデバイスを完全に無視し、待機タスクを一時停止し、最初に次のタスクを実行できることに気付きました。 IOデバイスが結果を返すまで待ってから、振り向いて、中断されたタスクを続けます。
したがって、JavaScriptには2つの実行方法があります。1つは、CPUが順番に実行され、以前のタスクが終了し、次のタスクが実行されることです。これは同期実行と呼ばれます。もう1つは、CPUが長い待機時間でタスクをスキップし、最初に後続のタスクを処理することです。これは非同期実行と呼ばれます。プログラマーは、どのような実行方法を採用するかを独立して選択します。
具体的には、非同期実行の動作メカニズムは次のとおりです。 (同期実行にも同じことが当てはまります。これは、非同期タスクなしで非同期実行と見なすことができるためです。)
(1)すべてのタスクがメインスレッドで実行され、実行コンテキストスタックを形成します。
(2)メインスレッドに加えて、「タスクキュー」もあります。システムは、非同期タスクを「タスクキュー」に入れてから、後続のタスクを実行し続けます。
(3)「実行スタック」のすべてのタスクが実行されると、システムは「タスクキュー」を読み取ります。この時点で、非同期タスクが待機状態を終了した場合、「タスクキュー」から実行スタックを入力し、実行を再開します。
(4)メインスレッドは、上記の3番目のステップを繰り返し続けます。
次の図は、メインスレッドとタスクキューの概略図です。
メインスレッドが空である限り、「タスクキュー」を読み取ります。これはJavaScriptの実行中のメカニズムです。このプロセスは継続的に繰り返されます。
3。イベントとコールバック関数
「タスクキュー」は本質的にイベントのキューです(メッセージのキューとしても理解されます)。 IOデバイスがタスクを完了すると、「タスクキュー」にイベントを追加し、関連する非同期タスクが「実行スタック」を入力できることを示します。メインスレッドは「タスクキュー」を読み取ります。つまり、内部のイベントを読むことを意味します。
「タスクキュー」のイベントには、IOデバイスからのイベントに加えて、ユーザーが生成するイベント(マウスクリック、ページスクロールなど)も含まれます。コールバック関数が指定されている限り、これらのイベントは発生したときに「タスクキュー」に入り、メインスレッドが読み取られるのを待ちます。
いわゆる「コールバック」は、メインスレッドで掛けられるコードです。非同期タスクは、コールバック関数を指定する必要があります。非同期タスクが「タスクキュー」から実行スタックに戻ると、コールバック関数が実行されます。
「タスクキュー」はファーストインファーストアウトデータ構造であり、最初にランク付けされ、メインスレッドに戻ることが推奨されるイベントがあります。メインスレッドの読み取りプロセスは基本的に自動です。実行スタックがクリアされている限り、「タスクキュー」の最初のイベントは自動的にメインスレッドに戻ります。ただし、後で説明した「タイマー」機能により、メインスレッドは実行時間を確認する必要があり、一部のイベントは指定された時間にメインスレッドに戻る必要があります。
4。イベントループ
メインスレッドは、「タスクキュー」からイベントを読み取ります。このプロセスは継続的にループしているため、実行中のメカニズム全体がイベントループとも呼ばれます。
イベントループをよりよく理解するには、以下の写真をご覧ください(フィリップロバーツのスピーチ「ヘルプ、イベントループで立ち往生している」から引用されています)。
上の図では、メインスレッドが実行されているとき、ヒープとスタックを生成します。スタック内のコードは、さまざまな外部APIを呼び出し、さまざまなイベント(クリック、ロード、完了)を「タスクキュー」に追加します。スタック内のコードが実行される限り、メインスレッドは「タスクキュー」を読み取り、それらのイベントに対応するコールバック関数を順番に実行します。
「タスクキュー」を読み取る前に、常に実行されるスタック内のコードを実行します。次の例をご覧ください。
コードコピーは次のとおりです。
var req = new xmlhttprequest();
req.open( 'get'、url);
req.onload = function(){};
req.onerror = function(){};
req.send();
上記のコードのReq.Sendメソッドは、サーバーにデータを送信するAJAX操作です。これは非同期タスクです。つまり、システムは、現在のスクリプトのすべてのコードが実行された後にのみ「タスクキュー」を読み取ることを意味します。したがって、それは次の執筆方法と同等です。
コードコピーは次のとおりです。
var req = new xmlhttprequest();
req.open( 'get'、url);
req.send();
req.onload = function(){};
req.onerror = function(){};
つまり、指定されたコールバック関数(onloadおよびonerror)の部分は、send()メソッドの前後に重要ではありません。これらは実行スタックの一部であり、「タスクキュー」を読む前にシステムが常に実行されるためです。
5。タイマー
非同期タスクの配置に加えて、「タスクキュー」には別の関数があります。これは、タイミングのイベントを配置することです。つまり、特定のコードが実行される期間を指定します。これは「タイマー」関数と呼ばれ、定期的に実行されるコードです。
タイマー関数は、主に2つの関数によって完了します:setimeout()とsetInterval()。それらの内部ランニングメカニズムはまったく同じです。違いは、前者によって指定されたコードが一度に実行され、後者は繰り返し実行されることです。以下は、主にsetimeout()について説明します。
Settimeout()は2つのパラメーターを受け入れます。1つ目はコールバック関数、2つ目は実行のミリ秒数です。
コードコピーは次のとおりです。
console.log(1);
setimeout(function(){console.log(2);}、1000);
console.log(3);
上記のコードの実行結果は1、3、2です。SettimeOut()は、1000ミリ秒後まで2行目を遅らせるためです。
Settimeout()の2番目のパラメーターが0に設定されている場合、現在のコードが実行された直後に指定されたコールバック関数(0ミリ秒間隔)が実行されることを意味します(実行スタックがクリアされます)。
コードコピーは次のとおりです。
setimeout(function(){console.log(1);}、0);
console.log(2);
上記のコードの実行結果は常に2と1です。これは、システムが2行目が実行された後にのみ「タスクキュー」でコールバック関数を実行するためです。
HTML5標準は、Settimeout()の2番目のパラメーターの最小値(最短間隔)が4ミリ秒未満でなければならないことを指定します。この値よりも低い場合、自動的に増加します。この前に、古いブラウザは最小間隔を10ミリ秒に設定します。
さらに、これらのDOMの変更(特にページの再レンダリングを含む部品)の場合、通常はすぐには実行されませんが、16ミリ秒ごとに実行されます。現時点では、RequestAnimationFrame()を使用する効果は、settimeout()よりも優れています。
Settimeout()は、イベントを「タスクキュー」に挿入するだけであることに注意してください。メインスレッドが指定するコールバック関数を実行する前に、現在のコード(実行スタック)が実行されるまで待つ必要があります。現在のコードが長い時間がかかる場合、待つのに時間がかかる場合があるため、Settimeout()で指定された時点でコールバック関数が実行されるという保証はありません。
6。Node.jsイベントループ
node.jsもシングルスレッドイベントループですが、その実行メカニズムはブラウザ環境のメカニズムとは異なります。
以下の図(著者@busyrich)を参照してください。
上記の数字によると、node.jsの実行中のメカニズムは次のとおりです。
(1)V8エンジンはJavaScriptスクリプトを解析します。
(2)解析コードはノードAPIを呼び出します。
(3)Libuvライブラリは、ノードAPIの実行を担当します。異なるタスクを異なるスレッドに割り当て、イベントループを形成し、タスクの実行結果を非同期的にV8エンジンに返します。
(4)V8エンジンは結果をユーザーに返します。
2つのメソッドSettimeOutとSetIntervalに加えて、node.jsは「タスクキュー」に関連する2つの他のメソッドを提供します:process.nexttickとsetimmediateも提供します。彼らは私たちが「タスクキュー」の理解を深めるのを助けることができます。
Process.nextTickメソッドは、メインスレッドが次回「タスクキュー」を読み取る前に、現在の「実行スタック」の最後にコールバック関数をトリガーできます。つまり、指定するタスクは、すべての非同期タスクの前に常に発生します。 Setimmediateメソッドは、現在の「タスクキュー」のテールでコールバック関数をトリガーします。つまり、メインスレッドが「タスクキュー」を次に読み取るときに、それが指定するタスクは常に実行されます(FN、0)。次の例を参照してください(StackOverFlow経由)。
コードコピーは次のとおりです。
process.nexttick(function a(){
console.log(1);
process.nexttick(function b(){console.log(2);});
});
setimeout(function timeout(){
console.log( 'Timeout Fired');
}、0)
// 1
// 2
//タイムアウトが発射されました
上記のコードでは、プロセスによって指定されたコールバック関数。項目Aは常に現在の「実行スタック」のテールでトリガーされるため、関数AはSettimeOutによって指定されたコールバック関数タイムアウトよりも最初に実行されるだけでなく、関数Bはタイムアウトよりも最初に実行されます。これは、複数のProcess.nextTickステートメントがある場合(それらがネストされているかどうかに関係なく)、それらはすべて現在の「実行スタック」で実行されることを意味します。
それでは、Setimmediateを見てみましょう。
コードコピーは次のとおりです。
setimmediate(function a(){
console.log(1);
setimmediate(function b(){console.log(2);});
});
setimeout(function timeout(){
console.log( 'Timeout Fired');
}、0)
// 1
//タイムアウトが発射されました
// 2
上記のコードには、2つのセットメディートがあります。最初のSetimmediateは、コールバック関数Aが現在の「タスクキュー」(次の「イベントループ」)のテールでトリガーされることを指定します。次に、SettimeOutは、現在の「タスクキュー」のテールでコールバック関数のタイムアウトがトリガーされることも指定しているため、出力結果では、タイムアウトが発生したタイムアウトが発生した後ろにランク付けされます。
これから重要な違いが得られました。複数のProcess.NextTickステートメントは常に一度に実行されますが、複数のセットメディートは実行するには複数回必要です。実際、これがまさにnode.jsバージョン10.0がSetimmediateメソッドを追加する理由です。それ以外の場合は、再帰的なProcess.nextTickのようなnextTickは無限になり、メインスレッドは「イベントキュー」をまったく読み取らません!
コードコピーは次のとおりです。
process.nexttick(function foo(){
process.nexttick(foo);
});
実際、再帰的なプロセスを書くと、nexttick、node.jsは、Setimmediateに変更するように求める警告を投げます。
さらに、Process.nexttickで指定されたコールバック関数は、この「イベントループ」でトリガーされ、Setimmediateは次の「イベントループ」でトリガーされていることを指定しているため、前者は常に後者よりも早く発生し、実行がより効率的であることは明らかです(「Task Queue」をチェックする必要はないため)。