JSはシングルスレッド
Javaや他のマルチスレッド言語とは異なり、JavaScriptはシングルスレッド言語です。ご存知のように、JavaScriptはブラウザ用のスクリプト言語として作られました。2つのスレッドがあり、一方がノードを追加し、もう一方がノードを削除するとします。両方のスレッドが同時に実行された場合、結果は混乱するに違いないので、JavaScript はシングルスレッド言語です。
同期と非同期
JavaScriptはシングルスレッドですが、同期と非同期があります。通常のajaxリクエストやsetTimeoutタイマーなどはすべて非同期タスクです。同期と非同期に関しては、誰もが少しは知っていると思います。この2つはプログラミングの基本的な概念です。抽象的な見方をすると、同期とは、メソッドや関数の実行後、プログラムがブロッキング状態になり、システムから結果が返されるまで、その後のコマンドを実行することです。非同期は、メソッドまたは関数の実行後、プログラムは、システムがメッセージを返すまで、次のコマンドを実行し続け、非ブロッキングであり、その後、対応する処理を行う、つまり、関連するイベントの数は、前のイベントの完了を待つ必要はありません行うには。イメージの観点から、生活の例を挙げると、朝、お粥の後に目を覚ますことです、お粥では、私はそれが同期している、煮るために待機するために何もしません。待たずに歯を磨いたり顔を洗ったりして、おかゆができたら食べに来る、それが非同期です。
同期と非同期の違いはなぜですか?
では、なぜ同期と非同期があるのでしょうか?それはJavaScriptがシングルスレッドであり、実行するタスクをキューに入れるからです。現在のタスクが終了すると、次のタスクが実行されます。私たちは皆、CPUがメモリよりも何百倍も速く、ハードドライブよりも100万倍も速いことを知っています。そのため、CPUは何度もメモリやハードディスクが結果を返すのを待たなければなりません。これは時間のかかるプロセスで、私はとっくの昔にあることを終えているのに、なぜこんなに長く待たされるのでしょう。結果を返したら私に知らせてください。同期タスクはメインスレッドで実行され、非同期タスクはタスクキューに入ります。非同期タスクは、タスク・キューがメイン・スレッドに非同期タスクの実行準備ができたことを通知すると、メイン・スレッドで実行されます。メイン・スレッドがタスク・キューから読み出す処理は、イベント・ループと呼ばれるループの中で続きます。
タスクキュー
setTimeout(() => {
console.log(1)
})
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve()
}).then(() => {
console.log(4)
})
// 2 3 4 1
なぜこの順番なのでしょうか?分析するために、JSコードの実行は、上から下に行ごとに実行されます。最初の実行はsetTimeoutこのコードは、これはタイマータスクであることがわかったので、内部の具体的な実装console.log(1)最初に他の場所に取り出し、後で実行する準備ができています。console.log(2)この文章を続けるので、最初に2を出力し、実行を続けると、Promiseに遭遇しました。このPromiseでは、console.log(3)とresolve()は同期的に実行されますが、thenのコードは非同期的に実行されることに注意してください。つまり、3が出力された後、console.log(4)を受け取り、後で実行されるように別の場所に置きます。さて、console.log(1)とconsole.log(4)を一つの場所に放り込んだわけですが、なぜ4が出力された後に1が出力されるのでしょうか?それは、1と4は大きな家という一つの場所に投げ込まれていますが、1と4は別々の部屋に投げ込まれているからです。1はマクロ・タスク・キューと呼ばれる部屋に、4はマイクロ・タスク・キューと呼ばれる別の部屋に投げ込まれました。どちらの部屋も人でいっぱいなので、友達が誰なのか簡単に見てみましょう。
マイクロタスクとマクロタスク
- : Promise ,process.nextTick 等
- : script, setTimeout, setInterval 等
ここでは、マクロが実行されるたびに、マイクロタスクキューにアクセスして、実行する必要のあるタスクがあるかどうかを確認します。マイクロタスクキューにタスクがある場合、マイクロタスクキューのタスクが最初に実行され、マイクロタスクキューはクリアされます。これが終わると、マクロ・キューの次のタスクが実行されます。百聞は一見にしかず、次の図をご覧ください。
要約すると、異なるタイプのタスクは対応するイベントキューに移動します。次のマクロタスクが実行されるたびに、まずマイクロタスクキューに行き、マイクロタスクキューが空になるまでチェックしてから、マクロタスクキューのタスクを実行します。
process.nextTickは次のイベントループの開始前に現在の実行スタックの最後でトリガーされ、setTimeoutはイベントループの開始後にトリガーされます。 次のコードをノードの実行環境に置くと、実行結果は 1 の前に 2 になります。
process.nextTick(() => {
console.log(1)
})
console.log(2)
// 2 1
上記のコードにsetTimeoutタイマーを配置し、出力を表示します。
setTimeout(() => {
console.log(3)
})
process.nextTick(() => {
console.log(1)
})
console.log(2)
// 2 1 3
これは、setTimeoutがマクロタスクで、nextTickがマイクロタスクだからです。つまり、マイクロタスクのキューが最初に空になり、process.nextTickコールバックが最初に実行されます。
マイクロタスクとマクロタスクを使って問題を解決
ここでは、同期・非同期のほか、マイクロ・タスク・キュー、マクロ・タスク・キューなど、コンテンツに関連するものを紹介してきましたが、さらに複雑なトピックを見てみましょう。
setTimeout(() => {
console.log(1)
})
setTimeout(() => {
new Promise((resolve, reject) => {
console.log(2)
resolve()
}).then(() => {
console.log(3)
})
})
console.log(4)
new Promise((resolve, reject) => {
console.log(5)
resolve()
}).then(() => {
console.log(6)
})
new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(8)
})
resolve()
}).then(() => {
console.log(9)
})
// 4 5 7 6 9 1 2 3 8
上に描いた図の線でこの問題を解いてください。入力する文字数を減らすために、console.log(1)を1とするような書き方をしました。最初の2つのsetTimeoutが実行され、123がマクロキューに入ります。4まで来ると、4が出力され、次に2つのPromiseが出力され、5が出力され、6がマイクロタスクキューに入れられます。次に7を出力し、8をマクロ・タスクに入れ、9をマイクロ・タスクに入れます。この時点で457がプリントされ、マイクロタスクには[6,9]が、マクロタスクには[1,2,3,8]があります。コードが初めて実行され、前述のようにスクリプト全体がマクロに相当します。マイクロタスクは空になり、マクロタスクが実行されます。マクロ キューの最初のタスクが選択され、1 が出力されます。マイクロタスクキューにまだ実行されていないタスクがあるかどうかを確認します。2を出力した後、これはPromiseなので、3をマイクロタスクキューに入れ、マクロタスクが実行されます。この時、マイクロタスクキューには[3]、マクロタスクキューには[8]があります。そして、マイクロタスクキューの実行に移り、3が出力されます。 最後に、もう一度マクロタスクキューの実行に戻り、8が出力されます。





