はじめに
マルチコアCPUを最大限に活かす「真の同時実行」
1. 並列処理とは?
並列処理(Parallel Processing) とは、
複数のCPUコアで複数のタスクを同時に実行する仕組みのことです。
たとえば、あなたのPCやスマートフォンは8コアCPUを搭載しているかもしれません。
その場合、1秒間に8個の重い計算を同時に処理できる可能性があります。
例えるなら
- 並行処理(Concurrency):1人のシェフが料理を順番に少しずつ進める
- 並列処理(Parallelism):複数のシェフが別々の料理を同時に作る
2. 並行処理との違い
| 特徴 | 並行処理(Concurrency) | 並列処理(Parallelism) |
|---|---|---|
| 実行単位 | 複数のタスクを切り替えて実行 | 複数のタスクを同時に実行 |
| CPU | 1コアでも可能 | 複数コアが必要 |
| 主な用途 | I/O待機の効率化 | CPU負荷の高い計算 |
| Kotlin Dispatcher | Dispatchers.IO |
Dispatchers.Default |
| 代表的な例 | ネット通信、ファイル操作 | 数値計算、圧縮、暗号化 |
並列処理は「物理的な同時実行」、
並行処理は「論理的な同時進行」です。
3. 並列処理の仕組み
現代のOSとCPUは「スレッド」を単位に動作しています。
- OSがスレッドをコアに割り当て
- 各スレッドが独立した命令列を同時実行
- Kotlinの
Dispatchers.Defaultがその管理を自動化
例えば4コアCPUの場合:
CPUコア1 ─ HeavyTask(1)
CPUコア2 ─ HeavyTask(2)
CPUコア3 ─ HeavyTask(3)
CPUコア4 ─ HeavyTask(4)
これにより、理論上は4倍速で処理できることになります。
4. Kotlinで並列処理を行う方法
Kotlinでは「コルーチン(Coroutine)」を利用して簡潔に並列処理を実現できます。
例:4つの重い計算を同時実行する
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun heavyCalculation(id: Int): Int {
println("Start Task $id on ${Thread.currentThread().name}")
delay(1000) // 時間のかかる計算を想定
println("End Task $id")
return id * id
}
fun main() = runBlocking {
val time = measureTimeMillis {
val results = (1..4).map {
async(Dispatchers.Default) { heavyCalculation(it) }
}.awaitAll()
println("結果: $results")
}
println("処理時間: ${time}ms")
}
実行結果(例)
Start Task 1 on DefaultDispatcher-worker-1
Start Task 2 on DefaultDispatcher-worker-2
Start Task 3 on DefaultDispatcher-worker-3
Start Task 4 on DefaultDispatcher-worker-4
End Task 1
End Task 2
End Task 3
End Task 4
結果: [1, 4, 9, 16]
処理時間: 1008ms
各タスクが異なるスレッドで**同時実行(並列処理)**されています。
5. CPUバウンド vs I/Oバウンド
並列処理を使うべきかどうかは、タスクの性質によります。
| 処理タイプ | 説明 | Dispatcher |
|---|---|---|
| CPUバウンド | CPUの演算を多く使う処理(例:画像解析、暗号化) | Dispatchers.Default |
| I/Oバウンド | ファイルやネット通信など待機時間が多い処理 | Dispatchers.IO |
withContext(Dispatchers.IO) { /* 並行処理 */ }
withContext(Dispatchers.Default) { /* 並列処理 */ }
6. 並列処理の効果を実感する例
逐次実行
for (i in 1..4) heavyCalculation(i)
→ 約4秒かかる
並列実行
(1..4).map { async(Dispatchers.Default) { heavyCalculation(it) } }.awaitAll()
→ 約1秒で完了(4タスクを並列処理)
7. 並列処理の注意点
| 問題 | 説明 | 対策 |
|---|---|---|
| Race Condition(競合) | 複数スレッドが同じ変数を書き換える |
Mutex や Atomic クラスを使う |
| デッドロック | スレッド同士が待ち合い停止 | リソースの取得順を統一 |
| スレッド過多 | コア数を超えるスレッド生成 |
Dispatchers.Default に任せる |
| キャンセル漏れ | コルーチンの中断が適切でない |
CoroutineScope を正しく管理 |
8. スレッドを可視化して理解する
コルーチンによるスレッド分配の概念図
Dispatchers.Defaultが自動的にコアへタスクを分配します。
9. 実務での活用例
- 大量データの一括処理(画像、ログ、暗号化)
- 複数モデルの機械学習推論
- 並列で複数APIを叩く集約処理
- Kotlin Multiplatform でのバックグラウンド並列実行
まとめ
| 要点 | 内容 |
|---|---|
| 並列処理とは | 複数コアでタスクを同時に実行すること |
| 並行との違い | 並列=物理的同時、並行=論理的同時 |
| Kotlinの手段 | コルーチン + Dispatchers.Default
|
| 適用シーン | 計算・圧縮・暗号化などCPUバウンド処理 |
| 注意点 | 競合・デッドロック・キャンセル管理 |