🌟

[JS]JavaScriptのイベントループ:非同期の仕組みを理解するための前知識

2023/04/20に公開

昨日は、以下のページで
"JavaScriptはシングルスレッド"だということと非同期について記述しました。

https://zenn.dev/airiswim/articles/14d8a9e87503b6

今回は、これらが"どのように動いているのか"をやっていきたいと思います。

"JavaScriptにおけるイベントループ"これは一番大事と言っても過言ではない!!!!

復習を、以下のタブにまとめておきます!

復習: 同期と非同期の違い

同期処理と非同期処理とは

非同期処理は複数のタスクを同時に実行することができる処理方式のことを指す。
一方、同期処理では、処理が完了するまで待機しなければならず、次の処理に進むことができない。

同期処理
複数のタスクを実行する際に、上から一つずつ順番にタスクが実行される方式。

  • merit:全体を把握しやすい
  • demerit:処理完了までに時間がかかり、ユーザーにとってはストレス

非同期処理
処理を一度バックグラウンドに移すことで、あるタスクを実行している最中でも
その処理を止めることなく、別のタスクを実行できる方式
=> これを実現するのが、コールバック、Promise , async/await
jsにおいてはajaxと言う技術がある。
※ここに関しては本題で掲載します。

  • merit:全体の処理速度を速められる
  • demerit:プログラムの全体像が複雑になりやすい

同期処理と非同期処理の比較

比較 同期処理 非同期処理
merit 全体を把握しやすい 全体の処理速度を速められる
demerit 処理完了までに時間がかかり、ユーザーにとってはストレス プログラムの全体像が複雑になりやすい
使用場面 処理結果を待つ必要があるもの 処理結果を待たなくても良いもの
処理結果がなくても進められるもの

⚠️非同期処理と並行処理の違い⚠️

  • 並行処理ができるかどうか。
    =文字どおり複数の処理を同時進行で行うことができるかということ。
    非同期処理は処理を止めることなく実行できるというだけの違い!!!

同期処理と非同期処理をもっとわかりやすく例えると…

(下手なイラストだけども描いてみたよ。)

同期処理タイプのホールスタッフのレストラン
1番テーブルのオーダー取ったら、キッチンにオーダーを伝えにいく
料理が出来上がるまでこのスタッフはキッチンの前から動かない。
2番テーブルから呼ばれても、聞こえていないタイプ。笑
1番テーブルの料理を運び終わってから2番テーブルへ行く。

非同期処理タイプのホールスタッフがいるレストラン
1番テーブルのオーダーをとって、キッチンに伝えに行く。
戻ってきて2、3番テーブルからもオーダーをとって、またキッチンに伝えに行く。
料理ができたら逐一運ぶ!!

シングルスレッド・マルチスレッド

シングルスレッドモデル
1つのスレッド(実行コンテキスト)で1つのタスクしか実行できないモデルのこと。
マルチスレッドモデル
複数のスレッドで同時に複数のタスクを実行することができる。
1つのスレッドで処理がブロックされた場合でも、別のスレッドで別のタスクを実行することができるもの。
[比較表]

シングルスレットモデル マルチスレットモデル
定義 1つの方程式に全ての変数を含む 複数の方程式に分割され、それぞれに対応する変数を含む
メリット 簡単に使用できる。
パラメーター数が少ないため、計算が速く、モデル選択が容易である。
変数ごとに最適なモデルを選択できる。
一部の変数についてのみデータがある場合、欠損データの扱いが容易である。
デメリット 複数の変数が相互に影響しあっている場合に、推定値が不正確になることがある。
欠落値に対する対応が困難である。
全体的にパラメーター数が多く、計算が遅くなり、過剰適合のリスクがある。
変数ごとに異なるモデルを選択するため、複雑なモデル選択が必要である。
採用言語 JavaScript、Ruby、Python、Lua、PHP Java、C++、C#、Python(一部)、Go、Rust、Scala

※言語ごとにモデルが固定されているわけではなく、
JSのようにシングルスレッドでも仕組みを使用しマルチスレッドにもできます。

- スレット:プログラムの実行単位。仕事の現場だと思ったらいい!

上記の内容を理解しやすい表現にすると、

  • javascriptはシングルスレッド = 現場は一つだけ
  • 一つだから管理することも簡単で早い!
  • でも、いろんな仕事を一期ですることは大変...。
    => 解決するには??ということで非同期処理が発生した。
    = シングルスレットのはやさ、簡単さを持ちつつ、処理を遅らせない仕組み!!!

JavaScriptにおけるイベントループを理解するために...

  • JavaScriptにおけるイベントループとは、ブラウザやNode.jsのランタイム環境において、非同期イベントの処理を制御する仕組みのこと

  • JavaScript は、"event loop"に基づく同時実行モデルを持ちます。

  • MDN: 並行モデルとイベントループ

これを理解するためにも
まずJavaScriptの動きを理解しましょう。

JavaScriptにおけるスレッド

シングルスレッド・マルチスレッド

シングルスレッドモデル
1つのスレッド(実行コンテキスト)で1つのタスクしか実行できないモデルのこと。
マルチスレッドモデル
複数のスレッドで同時に複数のタスクを実行することができる。
1つのスレッドで処理がブロックされた場合でも、別のスレッドで別のタスクを実行することができるもの。
[比較表]

シングルスレットモデル マルチスレットモデル
定義 1つの方程式に全ての変数を含む 複数の方程式に分割され、それぞれに対応する変数を含む
メリット 簡単に使用できる。
パラメーター数が少ないため、計算が速く、モデル選択が容易である。
変数ごとに最適なモデルを選択できる。
一部の変数についてのみデータがある場合、欠損データの扱いが容易である。
デメリット 複数の変数が相互に影響しあっている場合に、推定値が不正確になることがある。
欠落値に対する対応が困難である。
全体的にパラメーター数が多く、計算が遅くなり、過剰適合のリスクがある。
変数ごとに異なるモデルを選択するため、複雑なモデル選択が必要である。
採用言語 JavaScript、Ruby、Python、Lua、PHP Java、C++、C#、Python(一部)、Go、Rust、Scala

※言語ごとにモデルが固定されているわけではなく、
JSのようにシングルスレッドでも仕組みを使用しマルチスレッドにもできます。

- スレット:プログラムの実行単位。仕事の現場だと思ったらいい!

上記の内容を理解しやすい表現にすると、

  • javascriptはシングルスレッド = 現場は一つだけ
  • 一つだから管理することも簡単で早い!
  • でも、いろんな仕事を一期ですることは大変...。
    => 解決するには??ということで非同期処理が発生した。
    = シングルスレットのはやさ、簡単さを持ちつつ、処理を遅らせない仕組み!!!

スレットというのは、プログラムの実行単位。 => 仕事の現場だと思ったらいい!

ブラウザ環境では、複数のスレッドによる協同作業が行われている。

例えば、Webページの読み込み時には、メインスレッドによってHTMLとCSSが解析され、
DOMとCSSOMが構築
される。
その後、メインスレッドはJavaScriptエンジンを起動し、JavaScriptのコードが実行されます。

また、画像やフォントなどの外部リソースの読み込みは、別のスレッドで行われます。
これにより、メインスレッドがブロックされることなく、Webページの読み込みが高速化される。

さらに、Web WorkerやService Workerを使用することで、
JavaScriptのマルチスレッド処理が実現
される。
Web Workerは、メインスレッドとは別のスレッド上でJavaScriptのコードを実行でき、
Service WorkerはWebページとは独立して実行されるため、複数のタスクを同時に実行することができる

代表的なJavaScriptのスレッド

1. メインスレッド
JavaScriptのコードは、通常はメインスレッド上で実行される。
メインスレッドは、DOMの更新やイベントの処理、HTTPリクエストの送信など、JavaScriptコード以外の処理も行う。(要するに画面レンダリング、jsコード実行、イベント発火など)
そのため、JavaScriptの処理が長時間かかる場合には、メインスレッドがブロックされ、
ページのレスポンスが悪化することがある。

2. Web Worker
Web Workerは、JavaScriptのマルチスレッド処理を実現するための仕組み
Web Workerを使用すると、メインスレッドとは別のスレッド上でJavaScriptのコードを実行する。
Web Workerを使用することで、長時間かかる処理を別スレッドで実行し、メインスレッドをブロックしないようにすることができる。
要するに、よくバックグランドスレッドとか言われるもので、主に非同期のタスクの処理を行う。

などなど...。

JavaScriptはどのように動いているのか。

基本的には以下の動きをしています。

  1. Webページが読み込まれると、ブラウザーはHTMLコードを解析し、
    DOM(Document Object Model)ツリーを構築。
  2. JavaScriptコードがある場合、ブラウザーはJavaScriptエンジンを起動し、コードを解析
  3. JavaScriptエンジンは、
    コードをトークンに分割し、構文解析を行い、AST(Abstract Syntax Tree)を生成
  4. 生成されたASTを基に、JavaScriptエンジンはコードを実行
    この際、変数や関数などの宣言をメモリに保存し、実行時に必要な情報を取り出して利用します。
  5. 実行結果はブラウザーに表示され、ユーザーの操作に応じて、JavaScriptコードを再度実行することもあります。

JavaScriptエンジンとは

JavaScriptエンジンは、JavaScriptコードを解釈して実行するためのプログラム
もっと簡単にいうと、JavaScriptのコードを実行するために使用されるもの
以下のような流れになっている。
[ ソースコード →  JavaScriptエンジン → ブラウザが理解できるデータに変換 →  JavaScriptの実行]

主にWebブラウザーやNode.jsなどのランタイム環境に組み込まれていて
ブラウザーがWebページをロードするときにJavaScriptエンジンが起動してJavaScriptコードを処理する。

※ランタイム環境: プログラムが実行されるために必要な実行環境のこと

ブラウザーが利用しているJavaScriptエンジン

ブラウザ JavaScriptエンジン
Google Chrome V8
Mozilla Firefox SpiderMonkey
Apple Safari JavaScriptCore
Microsoft Edge Chakra
Opera V8
Internet Explorer Chakra

ただし、最近のブラウザでは複数のJavaScriptエンジンをサポートしている場合もあり、
例えばFirefoxはJITコンパイラーであるIonMonkeyと、実行速度の改善を図ったQuantum Flow JavaScriptエンジンを同時に利用しているとされている。

これらのエンジンは、JavaScriptコードを高速かつ効率的に処理するために最適化されている
また、Node.jsなどのサーバーサイドのJavaScriptランタイムでも、V8などのJavaScriptエンジンが使用されている。

JavaScript の処理を担う4つのメカニズムについて

スタックとキュー解説

スタック

  • Last in, Fast out. (LIFO)
  • 最後に保存したデータから取り出し、最初に保存したデータは最後に取り出されるデータ構造。

キュー

  • First in, First out. (FIFO)
  • 一番初めに保存したデータを最初に取り出すデータ構造。

コールスタック(Call Stack)

  • コールスタックは、JavaScriptエンジンが関数呼び出しの履歴を保持するために使用するスタック構造。
    "現在どの間ん数が実行されているのか、その関数の中でものどの関数が呼び出されたのか"
    追跡する。
  • メインスレットはコールスタックにより管理されているということ!!!

現在実行中の関数は、スタックの一番上に置かれ、関数が戻るとスタックから削除される。
同期的に実行されるJavaScriptコードは、すべてコールスタック内で実行。
■ コールスタックは、単一のスレッドで実行されるため、同時に実行できるコードは1つだけ。

タスクキュー

  • コールバック関数(非同期関数の引数に渡された関数)がこのタスクキューで管理される。
    => タスクキューは、非同期タスクが完了したときに実行されるコールバック関数を保持するキュー。

■ 非同期的な処理は、コールスタックから切り離され、タスクキューに置かれる。

WebAPI

APIとは

API(Application programming interface):

あるプログラムやサービスが提供する機能やデータに、外部からアクセスするためのインタフェースのこと。
複雑な機能を簡単に作成できるようになる。

  • webAPIもAPIの一種。
    (Webサーバーから提供されるAPIであり、HTTPプロトコルを使用してデータを受け渡すために設計されている。)

  • ブラウザが提供する機能であり、JSから見てこれは外部の機能!!!!

  • ブラウザとJavaScriptの間のやり取りを容易にする

  • ブラウザに組み込まれたAPIで、JavaScriptがブラウザとやり取りすることができる機能を提供している。
    ex. DOM APIはWeb APIの一部であり、HTMLの構造を変更することができる.

  • JavaScriptにおいて、このWebAPIに処理を任せるためシングルスレットにも関わらず非同期が実現できる。

ヒープ(Heap)

  • 簡単にいうとデータを格納する場所。
  • JavaScriptで使用されるメモリの一部。
    JavaScriptがデータを保存し、プログラムを実行するためのメモリを提供している。
    (JavaScriptオブジェクトや変数などのデータを保存する場所であり、動的に割り当てられる)

■ JavaScriptは、ヒープ上のオブジェクトを参照することができますが、ヒープ自体は直接制御することはできない。

本題: JavaScriptにおけるイベントループ

今日はここは大枠だけです。

次回から、具体的に入っていきます。

1. メインスレッドでコードを解釈(interpret)し始める。
メインスレッドは、JavaScriptコードを実行するスレッド。上から順にコードが実行される。
同期コードは直接コールスタックで実行される。

2. 同期コードは、即座に実行されます。
コールスタックは、関数の呼び出しを管理するスタックです。
関数が呼び出されると、コールスタックに追加され、関数が終了するとスタックから取り除かれます。
非同期コードは、ワーカースレッドに預けられる

3. 非同期コードは、すぐには実行されずワーカースレッド、タスクキュへ。
非同期コードは、別のスレッドで実行されます。
このスレッドは、ワーカースレッドと呼ばれます。
実行待ちの非同期タスクをタスクキューに投げる。

  1. 非同期処理が完了するまで、JavaScriptの実行をブロックすることはできない。
  2. 非同期処理が完了した後に実行する必要があるコードを、タスクキューに投げます。
    タスクキューは、非同期処理の完了を待つイベントを保持するキュー
    コールスタックの関数がなくなるまで実行。

コードの実行が完了するまで、コールスタックの中に関数が入っています。
コールスタックが空になるまで、関数を実行し続けます。
コールスタックが空っぽになると、タスクキューから非同期のタスクを入れる。

  1. コールスタックが空になると、イベントループが動き出します。
    イベントループは、タスクキューから非同期処理の完了を待つイベントを取り出し、コールスタックに追加します。
    非同期タスクを順次に実行。

  2. 非同期タスクは、イベントループによってコールスタックに追加されます。
    コールスタックに追加された非同期タスクは、順次実行されます。

以上がJavaScriptのイベントループの処理の流れです。


次回から具体的なないようになります。
今日はおやすみなさい!

明日はAWSのsummitです...!💌


https://developer.mozilla.org/ja/docs/Web/JavaScript/Event_loop

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

https://ja.javascript.info/event-loop#ref-3818

Discussion