概要JavaScriptが多くの言語と異なる点のひとつに、イベントループベースの同時実行モデルがあります。これは、JavaScript自体というよりも、ブラウザの仕組みにも関わりがあります。今回はそれについて書いてみようと思います。概要ラインタイムヒープスタックキューイベントループ応用例まとめ参考ラインタイムMDNの図を拝借すると、JavaScriptのラインタイムは以下のような要素で構成されています。ひとつずつ説明してゆきます。ヒープヒープはヒープですね。今回のテーマとは少し外れるので省略します。スタックJavaScriptで処理を呼び出すと、コールスタックが積み上がります。JavaScriptはシングルスレッドなので、一度にひとつの処理しか実行できません。以下のように関数を呼び出すような処理を考えます。subB();}main();これは、以下のようなスタックを形成します。mainのフレームmainから呼び出されたsubAのフレームsubAの実行が完了するとsubAのフレームはスタックからポップアウトmainから呼び出されたsubBのフレームsubBの実行が完了するとsubBのフレームはスタックからポップアウトすべての実行が完了するとmainのフレームはスタックからポップアウト上記のように同期的な関数実行では、処理がひとつずつ行われてゆくことがわかります。キューJavaScriptにおけるキューとは、次に実行すべき処理の一覧といえます。ここではsetTimeoutを使用した以下のコードを考えてみます。subB();}main();これは、以下のようなスタックとキューを形成します。mainのフレームmainから呼び出されたsubAのフレームsubAから呼び出されたsetTimeoutのフレームsetTimeoutの実行が完了するとsetTimeoutのフレームはスタックからポップアウト。ブラウザ側でタイマーの計算開始。mainから呼び出されたsubBのフレームsubBの実行が完了するとsubBのフレームはスタックからポップアウト4のタイマーが終了すると、ブラウザがイベントをキューに追加するキューに追加された処理をイベントループで検知、処理を実行するすべての実行が完了するとmainのフレームはスタックからポップアウト4でタイマーの処理がJavaScriptのメインスレッドを離れており、キューの仕組みがあることでノンブロッキングに処理が進んでいます。setTimeoutを連続で呼び出す場合も考えてみます。subB();}main();これは、以下のようなスタックとキューを形成します。上記のように、呼び出し自体は関数ひとつずつですが、処理自体はブラウザAPIによって同時実行され、完了したものからJavaScript側のキューにたまってゆくという仕組みです。これはスレッドなどで実現できる並列(parallel)処理とはいえませんが、実際の処理をJavaScriptランタイムの外にだすことで、並行(concurrent)に処理を実行していると言えます。(並行/並列/非同期/ノンブロッキングあたりは勘違いされやすいので、違いをおさえておくといいと思います)。イベントループ上記でも言及した、キューを検知できる仕組みをイベントループと呼びます。イベントループは、イベントを待機する実装のひとつで、MDNの例を借りるなら、以下のような実装に似ています。queue.processNextMessage();}シングルスレッドでのイベントループは、同時多発的に発生するイベントを、軽量に扱うのに向いています。これはプロセスが大量に立ち上がり、メモリやコンテキストスイッチで処理が重くなるのを防ぐことができるからです。(ただし、大量の計算など時間のかかる処理は苦手で、処理が終わるまで後続処理をブロックしてしまいます)。JavaScript以外でシングルスレッドのイベント駆動アーキテクチャを採用しているものとしては、nginxが有名です。nginxは、上記のような方針でC10K問題を解決しようとしていて、Node.jsもその文脈で話されているのをたまにみかけます。JavaScriptは、90年代のウェブブラウザという、十分なリソースがない環境のために開発された言語です。限られたCPUで少しでもまともに動くように実装されたのがシングルスレッド+イベントループというアーキテクチャだった、というのが正しい背景かと思います。イベントループのアーキテクチャは、ブラウザで発生する大量のイベントを処理するのに(少なくとも当時は)最適なユースケースであったと考えることができます。現在では、Goのように軽量スレッドを使用した方が線がいい場合も多いかもしれません。応用例ここで少し実際に役に立ちそうな例を見てみます。0)を使って処理をキューに外出しし、描画タイミングを早くするという手法があります。以下のように、DOMの変更処理のあとに、かなり時間のかかる処理があったとします。}}longTask();このようなコードを書くと、実際の画面変更はすぐには行われません。ブラウザで描画の処理が走る前に、10億回のループでスレッドが埋まってしまうので、描画が後回しにされてしまいます。メインスレッドを見るとlongTaskに約4秒かかっているので、その分ユーザーはインタラクションを待つことになるでしょう。0)で処理を一旦キューに押し込める」などがあります。0)こうすることで、描画処理を先に走らせることができるため、パフォーマンスを改善することができます。後続の処理をブロックしてしまうことには変わりないので、処理中の表示にしたり、Workerを使ったりするなども検討したほうがよさそうです。いずれにせよ、仕組みを理解しておくことは思わぬ事故へのリスクヘッジにもなると考えます。まとめ今回はJavaScriptの同時実行モデルについて紹介しました。以下の言葉を聞いてスッとイメージができればこの記事のまとめになっているかなと思います。スタック、キューシングルスレッド、イベントループ並列、並行お役に立てれば幸いです。専門性や実績を認定された公式パートナーです。ウェブサービス株式会社(DWS)公式ブログ記事URLをコピーしました。