Part III: マルチタスキングとスケジューリング¶
3.1 はじめに¶
Part II でスレッドによる並列処理を学びました。本章では、OS がどのように複数のタスクを切り替えて「同時に実行しているように見せる」かを学びます。ゲームループを題材に、協調的マルチタスキングの仕組みを 8 つの言語で実装します。
マルチタスキングとは¶
1 つの CPU コアでも、タスクを高速に切り替えることで複数のタスクが同時に動いているように見せる技術です。
CPU 時間: [Task1] → [Task2] → [Task3] → [Task1] → [Task2] → [Task3] → ...
2 つのスケジューリング方式¶
| 方式 | 説明 | 利点 | 欠点 |
|---|---|---|---|
| プリエンプティブ | OS がタスクを強制的に中断 | 公平性が保証される | コンテキストスイッチのオーバーヘッド |
| 協調的 | タスクが自発的に制御を譲る | オーバーヘッドが小さい | 行儀の悪いタスクがシステムを占有 |
3.2 共通の本質¶
ゲームループパターン¶
すべての言語で実装されるゲームループは、3 つのタスクを順次実行するパターンです:
while running:
1. Input — ユーザー入力の読み取り
2. Compute — ゲーム状態の更新
3. Render — 画面の描画
イベント同期パターン¶
複数のタスクを協調的に実行するため、バイナリイベント(フラグ)を使った同期パターンが全言語で共通しています:
Wait → フラグが True になるまでブロック
→ フラグを False にリセット(消費)
→ タスクを実行
→ フラグを True にセット(次のタスクに通知)
このパターンにより、ビジーウェイト(CPU を無駄に消費するループ)を避けつつ、タスク間の順序制御を実現します。
3.3 言語別実装比較¶
同期メカニズム¶
8 言語の同期メカニズムは、大きく 4 つのアプローチに分類できます:
| アプローチ | 言語 | メカニズム |
|---|---|---|
| イベントオブジェクト | Python | threading.Event |
| 条件変数 + ロック | Java, C#, Scala, F# | Condition / Monitor |
| 条件変数 + Mutex | Rust | Condvar + Mutex<bool> |
| STM(トランザクショナルメモリ) | Haskell | TVar + atomically |
| Atom + ロック | Clojure | atom + locking |
イベント同期の実装¶
関数型ファースト言語¶
Haskell 実装(STM)
import Control.Concurrent.STM
newtype ProcessorFreeEvent = ProcessorFreeEvent (TVar Bool)
newProcessorFreeEvent :: IO ProcessorFreeEvent
newProcessorFreeEvent = ProcessorFreeEvent <$> newTVarIO False
waitEvent :: ProcessorFreeEvent -> IO ()
waitEvent (ProcessorFreeEvent var) = atomically $ do
ready <- readTVar var
if ready
then writeTVar var False
else retry
signalEvent :: ProcessorFreeEvent -> IO ()
signalEvent (ProcessorFreeEvent var) = atomically $ writeTVar var True
Clojure 実装(atom + locking)
(defn create-event []
{:ready (atom false)
:lock (Object.)})
(defn signal-event! [event]
(locking (:lock event)
(reset! (:ready event) true)
(.notifyAll (:lock event))))
(defn wait-event! [event]
(locking (:lock event)
(while (not @(:ready event))
(.wait (:lock event)))
(reset! (:ready event) false)))
マルチパラダイム言語¶
Rust 実装(Condvar + Mutex)
use std::sync::{Condvar, Mutex};
pub struct ProcessorFreeEvent {
state: Mutex<bool>,
condvar: Condvar,
}
impl ProcessorFreeEvent {
pub fn new() -> Self {
ProcessorFreeEvent {
state: Mutex::new(false),
condvar: Condvar::new(),
}
}
pub fn wait(&self) {
let mut ready = self.state.lock().unwrap();
while !*ready {
ready = self.condvar.wait(ready).unwrap();
}
*ready = false;
}
pub fn signal(&self) {
let mut ready = self.state.lock().unwrap();
*ready = true;
self.condvar.notify_one();
}
}
Scala 実装(synchronized + @volatile)
class ProcessorFreeEvent:
private val lock = new Object
@volatile private var signaled = false
def waitForSignal(): Unit = lock.synchronized {
while !signaled do lock.wait()
}
def signal(): Unit = lock.synchronized {
signaled = true
lock.notifyAll()
}
def reset(): Unit = lock.synchronized {
signaled = false
}
F# 実装(Monitor + lock 式)
type ProcessorFreeEvent = {
Lock: obj
mutable Signaled: bool
}
module GameLoop =
let createProcessorFreeEvent () : ProcessorFreeEvent =
{ Lock = obj(); Signaled = false }
let waitForSignal (event: ProcessorFreeEvent) : unit =
lock event.Lock (fun () ->
while not event.Signaled do
Monitor.Wait(event.Lock) |> ignore
)
let signal (event: ProcessorFreeEvent) : unit =
lock event.Lock (fun () ->
event.Signaled <- true
Monitor.PulseAll(event.Lock)
)
OOP + 並行処理ライブラリ言語¶
Java 実装(ReentrantLock + Condition)
public static class ProcessorFreeEvent {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean signaled = false;
public void waitForSignal() {
lock.lock();
try {
while (!signaled) {
condition.await();
}
signaled = false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void signal() {
lock.lock();
try {
signaled = true;
condition.signal();
} finally {
lock.unlock();
}
}
}
C# 実装(Monitor)
public class ProcessorFreeEvent {
private readonly object _lock = new();
private bool _signaled;
public void WaitForSignal() {
lock (_lock) {
while (!_signaled) {
Monitor.Wait(_lock);
}
_signaled = false;
}
}
public void Signal() {
lock (_lock) {
_signaled = true;
Monitor.Pulse(_lock);
}
}
}
Python 実装(threading.Event)
from threading import Event
processor_free = Event()
processor_free.set()
class Task(Thread):
def __init__(self, func):
super().__init__()
self.func = func
def run(self):
while True:
processor_free.wait()
processor_free.clear()
self.func()
processor_free.set()
ゲームループの実装¶
タスク定義の比較¶
| 言語 | 定義方法 | 型 |
|---|---|---|
| Python | 関数 | Callable |
| Java | record Task(String, Runnable) |
Record |
| C# | record GameTask(string, Action) |
Record |
| Scala | case class GameTask(String, () => Unit) |
Case Class |
| F# | type GameTask = { Name: string; Action: unit -> unit } |
Record |
| Rust | struct GameTask { name: String, action: Box<dyn Fn()> } |
Struct + Trait Object |
| Haskell | data GameTask = GameTask { taskName :: String, taskAction :: IO () } |
Data Type |
| Clojure | クロージャ(暗黙的) | Map / Function |
ゲームループ本体¶
Rust 実装
pub fn game_loop(tasks: Vec<GameTask>, iterations: usize) {
for _ in 0..iterations {
for task in &tasks {
task.execute();
}
}
}
pub fn cooperative_game_loop(
tasks: Vec<Arc<GameTask>>,
event: Arc<ProcessorFreeEvent>,
iterations: usize,
) {
for _ in 0..iterations {
for task in &tasks {
task.execute();
event.signal();
thread::yield_now();
}
}
}
Haskell 実装
gameLoop :: [GameTask] -> Int -> IO ()
gameLoop tasks iterations =
replicateM_ iterations $ forM_ tasks taskAction
cooperativeGameLoop :: [GameTask] -> ProcessorFreeEvent -> Int -> IO ()
cooperativeGameLoop tasks event iterations =
replicateM_ iterations $ forM_ tasks $ \task -> do
taskAction task
signalEvent event
Java 実装
public class GameLoop {
private final Runnable inputTask;
private final Runnable computeTask;
private final Runnable renderTask;
private final BooleanSupplier continueCondition;
public void run() {
while (continueCondition.getAsBoolean()) {
inputTask.run();
computeTask.run();
renderTask.run();
}
}
}
3.4 比較分析¶
同期メカニズムの抽象度¶
高レベル ┌─────────────────────────────────┐
│ Haskell STM (retry/atomically) │ ← ロック概念なし
│ Python Event (set/wait/clear) │ ← ロックを隠蔽
├─────────────────────────────────┤
中レベル │ C# lock + Monitor │ ← ロックを構文で簡略化
│ Scala synchronized │
│ F# lock 式 + Monitor │
│ Clojure atom + locking │
├─────────────────────────────────┤
低レベル │ Java ReentrantLock + Condition │ ← 明示的なロック管理
│ Rust Condvar + Mutex │ ← 型システムで安全性保証
└─────────────────────────────────┘
デッドロックリスクの比較¶
| 言語 | リスク | 理由 |
|---|---|---|
| Haskell | なし | STM はトランザクショナル。競合時は自動リトライ |
| Clojure | 低い | atom は単一値の更新でロック競合が少ない |
| Rust | 低い | 型システムがロックの誤用を防止 |
| Python | 低い | Event は内部でロックを管理 |
| Scala / F# | 中程度 | synchronized の入れ子で発生可能 |
| Java / C# | 中程度 | 明示的ロックの順序管理が必要 |
コンテキストスイッチのコスト¶
コンテキストスイッチの発生時には以下のコストが発生します(全言語共通):
| コスト | 説明 |
|---|---|
| レジスタ退避・復元 | マイクロ秒単位 |
| キャッシュ無効化 | 可変コスト(L1/L2/L3) |
| TLB フラッシュ | アドレス変換キャッシュのリセット |
| スケジューラオーバーヘッド | 次に実行するタスクの選択 |
ただし、Haskell の Green Thread は GHC ランタイムが管理するため、OS レベルのコンテキストスイッチよりも軽量です。
3.5 実践的な選択指針¶
ゲームループ・リアルタイム処理に適した言語¶
最も適している:
- Rust — 予測可能なパフォーマンス、GC なし、
Condvarによる低レベル制御 - C# — ゲーム開発での実績(Unity)、
Monitorの簡潔な構文
理論的に優れている:
- Haskell — STM によるデッドロックフリーな設計。ただしリアルタイム性は GC に依存
プロトタイピング:
- Python —
threading.Eventの高レベル API で素早く実装可能
協調的 vs プリエンプティブの使い分け¶
| ユースケース | 推奨方式 | 言語の対応 |
|---|---|---|
| ゲームループ | 協調的 | 全言語で実装可能 |
| サーバー処理 | プリエンプティブ | OS スレッド(全言語) |
| GUI アプリ | イベント駆動 | Python (tkinter), C# (WPF), Java (Swing) |
| I/O 多重化 | 非同期 | Part VI-VII で詳述 |
3.6 まとめ¶
言語横断的な学び¶
- イベント同期パターンは普遍的 — wait/signal の二項対立はすべての言語で共通
- 抽象度のスペクトラム — Haskell STM(ロックなし)から Rust Condvar(明示的ロック)まで
- デッドロック回避の戦略は言語の思想に依存 — トランザクション(Haskell)、不変性(Clojure)、型システム(Rust)
- ゲームループは並行処理の基礎パターン — Input→Compute→Render の順次実行は全言語で同一構造
次のステップ¶
Part IV: タスク分解と並列パターン では、Fork/Join パターンとパイプラインパターンを学び、より複雑な並列処理の設計を探ります。
各言語の個別記事¶
| 言語 | 個別記事 |
|---|---|
| Python | Part III - マルチタスキング |
| Java | Part III - マルチタスキング |
| C# | Part III - マルチタスキング |
| Scala | Part III - マルチタスキング |
| F# | Part III - マルチタスキング |
| Rust | Part III - マルチタスキング |
| Haskell | Part III - マルチタスキング |
| Clojure | Part III - マルチタスキング |