🐱 うさねこ教室 Python と可観測性の教室

スレッドと並行処理

🐱 この章の目次

GIL のない世界

Python の GIL(Global Interpreter Lock) は、CPU バウンドな処理の並列実行を制限します。 JVM には GIL が存在せず、複数のスレッドが複数の CPU コアで同時に実行されます。 これが JVM サービスが高い並行性能を発揮できる根本的な理由です。

スレッドプールと ExecutorService

JVM では スレッドプール を使ってスレッドを効率的に再利用します。 ExecutorService はタスクをスレッドプールに投入し、結果を非同期に取得する標準 API です。

// Java: スレッドプールでタスクを並列実行
ExecutorService executor = Executors.newFixedThreadPool(4);

Future<String> future = executor.submit(() -> {
    // CPU バウンドな処理も並列に動く
    return heavyComputation();
});

String result = future.get();  // 結果を待つ
executor.shutdown();
# Python: concurrent.futures(GIL によりCPU並列は不可)
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(max_workers=4) as executor:
    future = executor.submit(heavy_computation)
    result = future.result()  # I/O には有効だが CPU 並列は GIL で制限

Kotlin コルーチン

Kotlin コルーチン は Python の asyncio に似た構造化された並行処理モデルです。 ただし、バックグラウンドで実際のスレッドプールを使うため、CPU バウンドな処理も並列に実行できます。

// Kotlin: 構造化された並行処理
import kotlinx.coroutines.*

suspend fun fetchData(): String = withContext(Dispatchers.IO) {
    // I/O 処理を別スレッドで実行
    httpClient.get("https://api.example.com/data")
}

fun main() = runBlocking {
    val results = listOf("url1", "url2", "url3").map { url ->
        async { fetchData() }  // 並行に実行
    }.awaitAll()
}
# Python: asyncio(I/O 並行のみ、シングルスレッド)
import asyncio

async def fetch_data(url: str) -> str:
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

async def main():
    results = await asyncio.gather(
        fetch_data("url1"), fetch_data("url2"), fetch_data("url3")
    )

Virtual Threads(Project Loom)

JDK 21 で導入された Virtual Threads(仮想スレッド) は、OS スレッドの上に軽量スレッドを実現します。 Python の asyncio のように大量の I/O 並行処理を扱えますが、既存のブロッキングコードをそのまま使えます。 async/await への書き換えが不要なため、移行コストがほぼゼロです。

// Java 21: Virtual Threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 100万スレッドも軽量に起動可能
    IntStream.range(0, 1_000_000).forEach(i ->
        executor.submit(() -> blockingHttpCall(i))
    );
}

Python との比較まとめ

PythonJVM
CPU 並列multiprocessing(プロセス分離)スレッドで直接並列
I/O 並行asyncio(async/await 必須)スレッド or Virtual Threads
軽量タスクコルーチン(シングルスレッド)Kotlin コルーチン(マルチスレッド)
GILありなし

JVM サービスがより多くの同時接続をシンプルなコードで処理できるのは、真のスレッド並列と Virtual Threads の組み合わせによるものです。