【入門編】 メインスレッドのブロッキングとタスク分割 – Webブラウザの仕組み実践ガイド

こんにちは!Webブラウザの仕組みの裏側を覗き見する世界へようこそ。フロントエンドの現場で泥水をすすりながらコードを書いていると、「あれ?なんか画面がカクカクするぞ……」「ボタンを押したのに、数ミリ秒フリーズする……」なんて恐怖体験に必ず直面します。

大丈夫です。あなたが悪いわけでも、PCの性能が低いわけでもありません。犯人はだいたい、ブラウザの「メインスレッド」で起きています。

今回は、このメインスレッドがなぜ息切れを起こしてしまうのか、そしてどうやってそれを救い出すのかを、お買い物やレストランの例えを交えて優しく紐解いていきましょう。

—

1. ブラウザのメインスレッドは「超優秀だけど不器用なワンオペ店員」

私たちが普段見ているWebページが画面に表示され、ボタンをクリックしてアニメーションが動くまでの裏側では、Webブラウザの「メインスレッド」という名のスタッフがたった一人で切り盛りしています。

このメインスレッドくん、仕事がめちゃくちゃ早いのですが、「同時にひとつのことしかできない(シングルスレッド)」という頑固な性格を持っています。彼がやっている主な仕事はこんな感じです:

  • HTMLを読んでDOMツリーを作る(解析)
  • CSSを読んでデザインを適用する(CSSOM構築)
  • 画面をピクセル単位で描き直す(レイアウトとペイント)
  • ユーザーからのクリックやスクロールのイベントを受け取る
  • そして、あなたが書いたJavaScriptのコードを実行する

さて、ここで想像してみてください。このワンオペ店員(メインスレッド)がレジに立っている時、突然、お客さん(JavaScript)から「これ、1万個分の段ボール箱を今すぐ全部検品して!」と無茶振りされたとします。

店員くんは真面目なので、「はい、わかりました!」と一生懸命その検品作業(重たいJavaScriptの処理)に没頭します。その間、レジの前には行列ができますよね。新しくやってきたお客さん(ユーザーのクリック)も、これからご飯を食べようとメニューを見たい人(画面のスクロールやアニメーション)も、ぜんぜん相手をしてもらえません。

これが、Webブラウザにおける「メインスレッドのブロッキング(画面のフリーズ・カクつき)」の正体です。

—

2. 長大なタスクがいかに画面を殺してしまうか

JavaScriptで重いループ処理を回したり、複雑な計算を一度にやらせたりすると、ブラウザは「今、JavaScriptの仕事で手一杯だから、画面を描き直す暇がないよ!」と悲鳴を上げます。

これが原因で、以下のようなイライラする現象が起きるわけです。

  • ボタンを押したのに、数秒間「カチッ」とも言わず固まる
  • ページをスクロールしようとすると、ガクンガクンとワープするように動く
  • アニメーションが途中でコマ送りになる

「じゃあ、重い処理をさせちゃいけないの?」というと、そうではありません。要は、「一気に全部やろうとするからいけない」のです。お買い物で例えるなら、レジで一度に100点の商品を精算しようとするから後ろの人を待たせるのであって、10点ずつに分けて精算すれば、合間に別のお客さんをさばく余裕が生まれますよね。

これをプログラミングの世界では「タスク分割(タイムスライシング)」と呼びます。

—

3. 優しいお助けマン:`setTimeout` と `requestIdleCallback`

ワンオペ店員くんに適度な休憩と「隙間時間」を与えるために、JavaScriptにはいくつかの優しい仕組み(API)が用意されています。代表的なものを2つ、初心者にも分かりやすい形でお伝えしますね。

① `setTimeout(…, 0)`:仕事を小分けにして「順番待ちの列」に戻る術

`setTimeout` は、指定した時間(0ミリ秒であっても)のあとに処理を予約するおなじみの関数ですが、実はこれ、メインスレッドの負担を減らす裏技としても使えます。

重い処理を小さな塊に切り分けて、「今の1個終わったから、残りは次の順番待ちの列(タスクキュー)に並び直すね!」とブラウザにバトンを渡すことができます。これによって、合間にブラウザが「あ、今のうちに画面を描き直しておこっと!」と息継ぎできるようになるのです。

② `requestIdleCallback`:「暇なときにやってね」とお願いする神ツール

さらにスマートなのが、`requestIdleCallback` です。これは文字通り、「ブラウザさんが今、画面描画も一段落してヒマ(Idle)になったタイミングで、この処理を実行してね」とお願いできる、めちゃくちゃ気の利いた機能です。

急ぎではないバックグラウンドのデータ送信や、ちょっとしたログの集計などは、すべてこのお兄さんに丸投げするのが現代のモダンなフロントエンド作法です。

—

4. 実践!タスク分割のコードを見てみよう

百聞は一見にしかず。例えば「100万回もの重い計算をこなす処理」があったとします。これを一気に実行すると画面が固まりますが、うまく分割してみましょう。

以下のコードを、あなたのブラウザのコンソールや、ちょっとしたHTMLファイルに貼り付けて試してみてください。

// 【重い処理のサンプル】 100万回まわるループを想定
const totalItems = 1000000;
let currentIndex = 0;

// 1回あたりに処理するアイテムの数(小分けの単位)
const chunkSize = 10000;

function processHeavyTask() {
const startTime = performance.now();

// 1回分のチャンク(小分け)だけ処理をする
while (currentIndex < totalItems) { // ここで何らかの重い処理をしていると仮定 // 例:数値の計算や、配列の加工など let dummy = Math.sqrt(currentIndex) 2; currentIndex++; // もし「1回の小分け上限」に達したら、一度手を止める! // ざっくり5ミリ秒くらい経ったら、一度メインスレッドを開放してあげます if (currentIndex % chunkSize === 0) { const elapsed = performance.now() - startTime; if (elapsed > 5) {
console.log(`ふぅ、ちょっと一息。現在 ${currentIndex} 件目まで完了しました。`);

// setTimeoutを使って、次の分の処理を「次の順番待ち」に回す
setTimeout(processHeavyTask, 0);
return; // 一旦この関数を抜け、メインスレッドに自由を与える!
}
}
}

console.log(“やったー!100万件すべての処理が完了しました!”);
}

// 処理をスタートするボタンを押されたと仮定して実行
console.log(“重い処理を開始します…”);
processHeavyTask();

// 💡 この処理が動いている最中でも、画面のボタンをクリックしたり
// スクロールしたりすることが「カクつかずに」可能になります!

このコードのミソは、`while` 文で全部一気にやらずに、`if` の条件に達したら `setTimeout` を挟んで一度関数を抜けている ところです。これにより、ブラウザのメインスレッドは「おっ、ちょっと手が空いたから画面のパラパラ漫画(描画)を更新しとこ!」と挟むことができるわけです。

—

まとめ:ブラウザに優しいコードは、ユーザーに優しい

フロントエンドの開発をしていると、「いかに動くコードを書くか」ばかりに目が行きがちですが、本当に優れたWebサイトやアプリは、「ブラウザの呼吸(メインスレッドの余裕)」までデザインされています。

もしあなたが書いたコードで画面がカクついたり、フリーズしたりしたら、こう自分に問いかけてみてください。

> 「今、ワンオペの店員くんに、全商品の検品を一人で全部やらせてないかな?」

もしそうなら、タスクを小分けにして、時々休憩(`setTimeout` や `requestIdleCallback`)を挟んであげてください。それだけで、あなたのWebサイトは見違えるほど滑らかで、ユーザーにとって心地よい最高の一品に生まれ変わります。

大丈夫、仕組みさえ分かってしまえばブラウザは最高の相棒です。今日も楽しくコードを書いていきましょう!

コメント

タイトルとURLをコピーしました