# 機能設計書 47-マルチスレッド

## 概要

Julia Base ライブラリにおけるマルチスレッド並列処理（`@threads`, `@spawn`, スレッドプール管理）を提供する機能の設計書である。

### 本機能の処理概要

**業務上の目的・背景**：計算集約型のワークロードにおいて、複数の CPU コアを活用して処理を並列化することで、実行時間を短縮する必要がある。Julia のマルチスレッド機構は `@threads` マクロによるデータ並列処理と `@spawn` マクロによるタスク並列処理を提供し、ユーザーが明示的にスレッドを管理することなく並列計算を記述できるようにする。

**機能の利用シーン**：大規模配列の並列処理、独立したデータの同時計算、I/O バウンド処理の並行実行、モンテカルロシミュレーションなどの並列化、スレッドプール（`:default` / `:interactive`）を使い分けた処理の優先度制御など。

**主要な処理内容**：
1. `Threads.@threads` マクロによる for ループの並列実行（`:dynamic`, `:static`, `:greedy` スケジューリング）
2. `Threads.@spawn` マクロによるタスクのスレッドプールへの割り当て
3. `Threads.threadid()` / `Threads.nthreads()` によるスレッド情報の取得
4. `Threads.threadpool()` / `Threads.threadpoolsize()` によるスレッドプール管理
5. `Threads.nthreadpools()` / `Threads.threadpooltids()` によるスレッドプール構成の照会
6. `Threads.maxthreadid()` によるスレッド数の下限取得
7. `Threads.ngcthreads()` による GC スレッド数の取得

**関連システム・外部連携**：Julia ランタイムのスレッド管理（C 実装）、OS のスレッド API（pthread 等）、`-t` / `--threads` 起動オプションによるスレッド数の設定。

**権限による制御**：マルチスレッド処理に特段の権限制御はない。スレッド数はプロセス起動時に `-t` オプションで指定する。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CLI / REPL | 主画面 | @threads/@spawn の対話的実行 |

## 機能種別

並列処理 / ランタイム機能

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| schedule | Symbol | No | @threads のスケジューリング方式（:dynamic, :static, :greedy）| 有効なシンボル |
| threadpool | Symbol | No | @spawn のスレッドプール（:default, :interactive, :samepool）| 有効なシンボル |
| pool | Symbol | No | nthreads/threadpoolsize のプール指定 | :default または :interactive |

### 入力データソース

マクロ引数または関数引数としてユーザーコードから直接渡される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| task | Task | @spawn の戻り値（非同期タスク） |
| - | Nothing | @threads の戻り値（同期完了後に nothing） |
| tid | Int | threadid() の戻り値 |
| n | Int | nthreads() / threadpoolsize() の戻り値 |

### 出力先

関数・マクロの戻り値としてユーザーコードに返される。

## 処理フロー

### 処理シーケンス

```
1. @threads の場合:
   1-1. スケジューリング方式の判定
   1-2. スレッドプールサイズ分のタスクを作成
   1-3. イテレーション空間の分割（方式による）
   1-4. 各タスクをスケジュール
   1-5. 全タスクの完了を待機
   1-6. 例外があれば CompositeException を throw

2. @spawn の場合:
   2-1. Task オブジェクトの作成（sticky=false）
   2-2. スレッドプールの割り当て
   2-3. @sync 変数への登録（存在する場合）
   2-4. タスクのスケジュール
   2-5. Task オブジェクトの返却
```

### フローチャート

```mermaid
flowchart TD
    A[@threads / @spawn] --> B{種別}
    B -->|@threads| C{スケジュール方式}
    C -->|:dynamic| D[スレッド数分のタスク作成]
    C -->|:static| E[各スレッドに固定割当]
    C -->|:greedy| F[Channel経由の貪欲割当]
    D --> G[イテレーション分割]
    E --> H[均等分割 + スレッド固定]
    F --> I[Channel から取得]
    G --> J[全タスク schedule + wait]
    H --> J
    I --> J
    J --> K{例外?}
    K -->|Yes| L[CompositeException]
    K -->|No| M[正常完了]
    B -->|@spawn| N[Task 作成 sticky=false]
    N --> O[threadpool 割当]
    O --> P[schedule]
    P --> Q[Task 返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-47-01 | @threads デフォルトプール | @threads は常に :default プールのスレッドを使用する | @threads 使用時 |
| BR-47-02 | :static のネスト禁止 | :static スケジューリングは @threads のネストや非メインスレッドからの呼び出しが禁止 | @threads :static 使用時 |
| BR-47-03 | :dynamic のデフォルト | スケジュール引数なしの @threads は :dynamic を使用する | Julia 1.8 以降 |
| BR-47-04 | @spawn のスレッドプール | @spawn はデフォルトで :default プールにタスクを割り当てる | @spawn 使用時 |
| BR-47-05 | タスクマイグレーション | :static 以外では threadid() は実行中に変化し得る | :dynamic / :greedy 使用時 |
| BR-47-06 | :greedy の非均一ワークロード | :greedy は個々の反復のワークロードが不均一な場合に適する | :greedy 使用時 |

### 計算ロジック

`:dynamic` / `:static` スケジューリングでは、イテレーション空間をスレッド数で均等分割する（`divrem` による計算）。余りは先頭のスレッドに分配される。`:greedy` では Channel を介して各スレッドが次のアイテムを取得する。

## データベース操作仕様

### 操作別データベース影響一覧

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| CompositeException | Exception | @threads 内のタスクで例外発生 | タスク内で try-catch |
| ArgumentError | ArgumentError | 無効なスケジュール引数 | :dynamic/:static/:greedy を指定 |
| ErrorException | Exception | :static のネスト使用 | :dynamic に変更 |
| ArgumentError | ArgumentError | 無効なスレッドプール名 | :default/:interactive/:samepool を指定 |

### リトライ仕様

自動リトライは行われない。エラー発生時はアプリケーション層で処理する。

## トランザクション仕様

@threads は全タスクの完了を保証する同期操作である。一部のタスクが失敗しても、全タスクの完了を待ってから CompositeException を throw する。

## パフォーマンス要件

- @threads のオーバーヘッドは数マイクロ秒〜数十マイクロ秒
- 反復ごとの処理時間が 10 マイクロ秒以上の場合に並列化の効果が見込まれる
- :dynamic は :static より柔軟だが、わずかにオーバーヘッドが大きい
- :greedy は不均一ワークロードに適するが、Channel のオーバーヘッドがある

## セキュリティ考慮事項

- マルチスレッド環境ではデータ競合が発生し得る。共有データへのアクセスにはロックまたはアトミック操作が必要
- threadid() はタスクマイグレーションにより変化し得るため、スレッド ID をバッファのインデックスに使用するのは安全でない

## 備考

- スレッド数は `julia -t N` または `JULIA_NUM_THREADS=N` 環境変数で設定する
- `:interactive` プールは I/O バウンド処理用、`:default` プールは計算集約処理用
- `:foreign` プールは外部から作成されたスレッド（GC スレッド等）を表す
- `@threads :greedy` は Julia 1.11 で追加された

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

マルチスレッド機構の中核はスレッドプールの概念である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | threadingconstructs.jl | `base/threadingconstructs.jl` | **1-7行目**: export 宣言。公開 API は `threadid`, `nthreads`, `@threads`, `@spawn`, `threadpool`, `nthreadpools` |
| 1-2 | threadingconstructs.jl | `base/threadingconstructs.jl` | **62-89行目**: `_nthreads_in_pool`, `_tpid_to_sym`, `_sym_to_tpid` - スレッドプール ID とシンボルの変換関数 |

**読解のコツ**: スレッドプールは 3 種類（`:interactive`=0, `:default`=1, `:foreign`=-1）。プール ID は Int8 で管理される。

#### Step 2: スレッド情報取得関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | threadingconstructs.jl | `base/threadingconstructs.jl` | **36行目**: `threadid()` - `ccall(:jl_threadid)` で現在のスレッド ID を取得（1-based） |
| 2-2 | threadingconstructs.jl | `base/threadingconstructs.jl` | **47行目**: `maxthreadid()` - atomic_pointerref で全スレッド数の下限を取得 |
| 2-3 | threadingconstructs.jl | `base/threadingconstructs.jl` | **96-99行目**: `threadpool(tid)` - `ccall(:jl_threadpoolid)` でスレッドプール名を取得 |
| 2-4 | threadingconstructs.jl | `base/threadingconstructs.jl` | **124行目**: `nthreadpools()` - グローバル変数 `jl_n_threadpools` から取得 |
| 2-5 | threadingconstructs.jl | `base/threadingconstructs.jl` | **136-145行目**: `threadpoolsize(pool)` - 指定プールのスレッド数を取得 |
| 2-6 | threadingconstructs.jl | `base/threadingconstructs.jl` | **169行目**: `ngcthreads()` - GC スレッド数を取得 |

#### Step 3: @threads マクロを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | threadingconstructs.jl | `base/threadingconstructs.jl` | **171-201行目**: `threading_run(fun, static)` - @threads の実行エンジン。タスク作成 → schedule → wait のサイクル |
| 3-2 | threadingconstructs.jl | `base/threadingconstructs.jl` | **203-224行目**: `_threadsfor(iter, lbody, schedule)` - @threads マクロの展開コード生成 |
| 3-3 | threadingconstructs.jl | `base/threadingconstructs.jl` | **226-241行目**: `greedy_func` - :greedy スケジューリングの実装。Channel でアイテムを分配 |
| 3-4 | threadingconstructs.jl | `base/threadingconstructs.jl` | **243-284行目**: `default_func` - :dynamic/:static スケジューリングの実装。divrem でイテレーション分割 |
| 3-5 | threadingconstructs.jl | `base/threadingconstructs.jl` | **413-439行目**: `@threads` マクロ本体。引数パース → _threadsfor 呼出 |

**主要処理フロー**:
1. **171-176行目**: `threading_run` が `:default` プールのスレッド数分の Task を作成
2. **178-179行目**: `static` フラグに応じて `sticky` を設定し、スレッド固定または自由移動
3. **181-186行目**: `ccall(:jl_set_task_tid)` でスレッド割当、または `jl_set_task_threadpoolid` でプール割当
4. **189-193行目**: 全タスクを schedule し、Base._wait で完了を待機
5. **197-200行目**: 失敗タスクを収集し CompositeException を throw

#### Step 4: @spawn マクロを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | threadingconstructs.jl | `base/threadingconstructs.jl` | **441-449行目**: `_spawn_set_thrpool` - タスクにスレッドプールを設定するヘルパー |
| 4-2 | threadingconstructs.jl | `base/threadingconstructs.jl` | **495-535行目**: `@spawn` マクロ本体。Task 作成 → sticky=false → プール設定 → @sync 登録 → schedule |

### プログラム呼び出し階層図

```
@threads :dynamic for i in 1:N ... end
    └─ _threadsfor(iter, lbody, :dynamic)
        └─ default_func(itr, lidx, lbody)  ... 分割関数生成
            └─ threading_run(fun, false)
                ├─ Task(() -> fun(tid))     ... スレッド数分
                ├─ jl_set_task_threadpoolid ... :default プール割当
                ├─ schedule(t)             ... 各タスク開始
                ├─ Base._wait(t)           ... 各タスク完了待ち
                └─ CompositeException      ... 失敗時

@threads :greedy for i in iter ... end
    └─ _threadsfor(iter, lbody, :greedy)
        └─ greedy_func(itr, lidx, lbody)
            ├─ Channel(threadpoolsize()) ... アイテム分配
            └─ threading_run(fun, false)

@spawn [:default] expr
    ├─ Task(() -> expr)
    ├─ task.sticky = false
    ├─ _spawn_set_thrpool(task, :default)
    │      └─ ccall(:jl_set_task_threadpoolid)
    ├─ put!(sync_var, task)  ... @sync 存在時
    └─ schedule(task)
```

### データフロー図

```
[入力]                      [処理]                              [出力]

for ループ式 ──────────▶ @threads ──▶ threading_run      ──▶ 並列実行完了
                              ├─ :static  ──▶ 均等分割+固定
                              ├─ :dynamic ──▶ 均等分割+自由
                              └─ :greedy  ──▶ Channel分配

式 expr     ──────────▶ @spawn  ──▶ Task + schedule     ──▶ Task オブジェクト

-           ──────────▶ threadid()                       ──▶ Int (1-based)
:default    ──────────▶ nthreads(:default)               ──▶ Int
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| threadingconstructs.jl | `base/threadingconstructs.jl` | ソース | @threads/@spawn マクロ、スレッド情報関数 |
| task.jl | `base/task.jl` | ソース | Task の基本操作（schedule, wait 等） |
| channels.jl | `base/channels.jl` | ソース | Channel（:greedy スケジューリングで使用） |
| lock.jl | `base/lock.jl` | ソース | ReentrantLock（スレッド間同期） |
| locks-mt.jl | `base/locks-mt.jl` | ソース | SpinLock（低レベルスレッド同期） |
