# 機能設計書 49-同期プリミティブ

## 概要

Julia Base ライブラリにおける同期プリミティブ（ReentrantLock, SpinLock, Condition, Semaphore, Event, Lockable, OncePerProcess/Thread/Task 等）を提供する機能の設計書である。

### 本機能の処理概要

**業務上の目的・背景**：マルチスレッド・マルチタスク環境において、共有リソースへの安全なアクセスやタスク間の同期を実現するために、低レベルの同期プリミティブが必要である。本機能はロック、条件変数、セマフォ、イベント、およびワンタイム初期化の仕組みを提供し、並行プログラミングの基盤となる。

**機能の利用シーン**：共有データ構造への排他的アクセス（ReentrantLock）、高速なスピンウェイト同期（SpinLock）、タスク間のシグナリング（Condition / Event）、同時アクセス数の制限（Semaphore）、遅延初期化の安全な実行（OncePerProcess / OncePerThread / OncePerTask）、ロックとデータの関連付け（Lockable）など。

**主要な処理内容**：
1. `ReentrantLock` による再入可能なタスクレベルロック
2. `SpinLock` / `PaddedSpinLock` によるスピンロック
3. `Condition` / `Threads.Condition` による条件変数
4. `Semaphore` によるカウンティングセマフォ
5. `Event` によるレベルトリガーイベント
6. `Lockable` によるロック付きデータラッパー
7. `OncePerProcess` / `OncePerThread` / `OncePerTask` によるワンタイム初期化
8. `@lock` / `@lock_nofail` マクロによるロックスコープ管理
9. `@acquire` マクロによるセマフォスコープ管理

**関連システム・外部連携**：Julia ランタイムのアトミック操作機構、OS のスレッドスケジューラ、GC（ファイナライザの抑制）と連携する。

**権限による制御**：同期プリミティブの使用に権限制御はない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | CLI / REPL | 主画面 | ロック・同期プリミティブの対話的使用 |

## 機能種別

並行処理基盤 / 同期制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| sem_size | Int | Yes (Semaphore) | セマフォのカウンタ上限 | > 0 |
| autoreset | Bool | No (Event) | イベントの自動リセット（デフォルト: false） | - |
| init | Function | Yes (Once*) | 初期化関数 | - |

### 入力データソース

ユーザーコードから直接引数として渡される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| lock | ReentrantLock / SpinLock | ロックオブジェクト |
| cond | Condition / Threads.Condition | 条件変数 |
| sem | Semaphore | セマフォ |
| event | Event | イベント |
| lockable | Lockable{T,L} | ロック付きデータ |
| value | T | OncePerProcess/Thread/Task の初期化結果 |

### 出力先

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

## 処理フロー

### 処理シーケンス

```
1. ReentrantLock:
   1-1. lock: trylock 試行 → 失敗時スピン → パーク → wait
   1-2. unlock: reentrancy_cnt デクリメント → 0なら解放 → パーク中タスク通知

2. SpinLock:
   2-1. lock: trylock ループ → CPU suspend → GC safepoint
   2-2. unlock: atomicswap でリリース → CPU wake

3. Condition:
   3-1. wait: waitq に登録 → ロック解放 → suspend → ロック再取得
   3-2. notify: waitq から取り出し → schedule

4. Semaphore:
   4-1. acquire: カウンタ >= max なら wait → カウンタインクリメント
   4-2. release: カウンタデクリメント → notify

5. Event:
   5-1. wait: set フラグ確認 → false ならロック + wait
   5-2. notify: set = true → notify 全待機者（autoreset なら1人だけ）

6. OncePerProcess:
   6-1. 呼出: state 確認 → PerStateInitial ならロック + 初期化 → state = PerStateHasrun
```

### フローチャート

```mermaid
flowchart TD
    A[同期操作] --> B{プリミティブ種別}
    B -->|ReentrantLock| C{trylock?}
    C -->|成功| D[reentrancy_cnt++]
    C -->|失敗| E[スピン + パーク]
    E --> F[cond_wait で待機]
    F --> C
    B -->|SpinLock| G{trylock?}
    G -->|成功| H[owned = 1]
    G -->|失敗| I[CPU suspend + safepoint]
    I --> G
    B -->|Condition| J{wait or notify?}
    J -->|wait| K[waitq 登録 + unlock + suspend]
    J -->|notify| L[waitq から schedule]
    B -->|Semaphore| M{acquire or release?}
    M -->|acquire| N{cnt < max?}
    N -->|Yes| O[cnt++]
    N -->|No| P[wait]
    M -->|release| Q[cnt-- + notify]
    B -->|Event| R{wait or notify?}
    R -->|wait| S{set?}
    S -->|Yes| T[即座に返却]
    S -->|No| U[lock + wait]
    R -->|notify| V[set=true + notify]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | ReentrantLock の再入可能性 | 同一タスクからの複数回 lock はカウンタインクリメントで対応 | lock 呼出時 |
| BR-49-02 | ファイナライザの抑制 | ReentrantLock/SpinLock の lock 中はファイナライザが無効化される | lock 取得中 |
| BR-49-03 | SpinLock の非再入 | SpinLock は再入不可。同一タスクからの二重 lock はデッドロック | SpinLock 使用時 |
| BR-49-04 | Condition のロック要件 | Threads.Condition の wait/notify はロック保持状態でのみ呼び出し可能 | Threads.Condition 使用時 |
| BR-49-05 | Event のレベルトリガー | Event は一度 notify されると reset まで set 状態を維持する | Event 使用時 |
| BR-49-06 | Event の autoreset | autoreset=true では notify ごとに最大1タスクが起床する | autoreset=true |
| BR-49-07 | OncePerProcess の一回性 | 初期化関数はプロセス内で正確に一度だけ実行される | OncePerProcess 使用時 |
| BR-49-08 | Lockable のアクセス制御 | Lockable の値はロック保持時のみアクセス可能（assert_havelock） | getindex 呼出時 |

### 計算ロジック

ReentrantLock は LOCKED_BIT（0b01）と PARKED_BIT（0b10）の2ビットで状態を管理する。trylock は CAS（Compare-And-Swap）操作でアトミックにロックを取得する。SpinLock は test-and-test-and-set アルゴリズムで、最大約30スレッドの競合まで効率的に動作する。

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

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

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ConcurrencyViolationError | Exception | ロック未保持で assert_havelock が呼ばれた | lock を取得してから操作 |
| ErrorException | Exception | unlock の呼び出し回数が lock と不一致 | lock/unlock の対応を確認 |
| ErrorException | Exception | 別スレッドからの unlock | ロックを取得したタスクから unlock |
| ArgumentError | ArgumentError | Semaphore のサイズが 0 以下 | 正の整数を指定 |
| ErrorException | Exception | OncePerProcess の初期化が過去に失敗 | 初期化関数のエラーを修正 |

### リトライ仕様

ロック取得の失敗は内部的にスピン + パークでリトライされる。OncePerProcess の初期化失敗後のリトライは不可（PerStateErrored で永続的にエラー）。

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

lock/unlock の対は論理的なクリティカルセクションを形成する。`@lock` マクロは try-finally で unlock を保証する。`@lock_nofail` は例外が発生しないことが保証された場合のみ使用可能。

## パフォーマンス要件

- ReentrantLock: 競合なしの lock/unlock はナノ秒オーダー（CAS 1回）
- SpinLock: 競合なしの lock/unlock はナノ秒オーダー（atomicswap 1回）
- SpinLock: 約30スレッドまでの競合で効率的
- Semaphore: Threads.Condition ベースのため、ReentrantLock と同等のオーバーヘッド
- Event: アトミック操作による高速な set 確認

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

- デッドロック・ライブロックの回避はユーザーの責任
- SpinLock はファイナライザを無効化するため、長時間保持するとメモリリークの原因となる
- OncePerProcess の初期化関数内で例外が発生すると、以降の全呼び出しが PerStateErrored となる

## 備考

- `ReentrantLock` はキャッシュライン分のパディングを含み、false sharing を最小化する
- `PaddedSpinLock` はキャッシュライン（64バイト）のパディングを明示的に追加する
- `Base.Condition`（AlwaysLockedST ベース）はスレッドセーフではない。スレッドセーフが必要な場合は `Threads.Condition` を使用する
- `OncePerThread` はスレッド ID でインデックスされる AtomicMemory を使用する
- `OncePerTask` は task_local_storage を使用する

---

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

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

### 推奨読解順序

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

各同期プリミティブの内部構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | condition.jl | `base/condition.jl` | **18行目**: `AbstractLock` 抽象型。lock, trylock, unlock, islocked のインターフェース |
| 1-2 | condition.jl | `base/condition.jl` | **63-70行目**: `GenericCondition{L}` 構造体。waitq（IntrusiveLinkedList{Task}）と lock（L）で構成 |
| 1-3 | lock.jl | `base/lock.jl` | **48-86行目**: `ReentrantLock` 構造体。`@atomic locked_by`, `reentrancy_cnt`, `@atomic havelock`（LOCKED_BIT/PARKED_BIT）, `cond_wait`（ThreadSynchronizer）。キャッシュラインパディング付き |
| 1-4 | locks-mt.jl | `base/locks-mt.jl` | **41-45行目**: `SpinLock` 構造体。`@atomic owned` のみ |
| 1-5 | locks-mt.jl | `base/locks-mt.jl` | **60-70行目**: `PaddedSpinLock` 構造体。キャッシュライン（64バイト）のパディング付き |

**読解のコツ**: ReentrantLock の `havelock` フィールドは 2 ビットのビットフィールドとして使用される。LOCKED_BIT（0b01）はロック状態、PARKED_BIT（0b10）はパーク待ちタスクの存在を示す。`@atomic` / `@atomicreplace` / `@atomicswap` アノテーションはメモリオーダリングを指定するアトミック操作。

#### Step 2: ReentrantLock の操作を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | lock.jl | `base/lock.jl` | **165-183行目**: `trylock(rl)` - 再入チェック → CAS でロック取得。GC.disable_finalizers() に注意 |
| 2-2 | lock.jl | `base/lock.jl` | **194-246行目**: `lock(rl)` - trylock 失敗時の slowlock。スピン（MAX_SPIN_ITERS=40回）→ PARKED_BIT 設定 → cond_wait で wait |
| 2-3 | lock.jl | `base/lock.jl` | **269-302行目**: `unlock(rl)` - reentrancy_cnt デクリメント → 0 なら locked_by=nothing, havelock リリース → パーク中タスクに notify |

**主要処理フロー**:
1. **165-172行目**: `trylock` は同一タスクの再入を `locked_by === ct` で高速チェック
2. **173-183行目**: `_trylock` は GC ファイナライザ無効化 → CAS → 成功なら locked_by 設定
3. **195-244行目**: `slowlock` のループ: state 読み取り → LOCKED_BIT==0 なら CAS → PARKED_BIT==0 ならスピン → パーク
4. **272-302行目**: `_unlock`: reentrancy_cnt==0 なら locked_by=nothing → CAS でリリース → PARKED_BIT あれば notifywaiters

#### Step 3: SpinLock の操作を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | locks-mt.jl | `base/locks-mt.jl` | **76-85行目**: `lock(l::AbstractSpinLock)` - trylock ループ + jl_cpu_suspend + jl_gc_safepoint |
| 3-2 | locks-mt.jl | `base/locks-mt.jl` | **87-97行目**: `trylock(l)` - owned==0 チェック → GC disable → atomicswap :acquire |
| 3-3 | locks-mt.jl | `base/locks-mt.jl` | **99-106行目**: `unlock(l)` - atomicswap :release → GC enable → jl_cpu_wake |

#### Step 4: Condition / Event / Semaphore を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | condition.jl | `base/condition.jl` | **136-148行目**: `wait(c::GenericCondition)` - waitq に登録 → unlockall → wait() → relockall |
| 4-2 | condition.jl | `base/condition.jl` | **159-170行目**: `notify(c)` - waitq から popfirst! → schedule |
| 4-3 | lock.jl | `base/lock.jl` | **633-638行目**: `Event` 構造体。notify Condition + autoreset + @atomic set |
| 4-4 | lock.jl | `base/lock.jl` | **640-658行目**: `wait(e::Event)` - set フラグの atomicswap / 確認 → Condition wait |
| 4-5 | lock.jl | `base/lock.jl` | **660-675行目**: `notify(e::Event)` - autoreset ? 1人だけ notify : 全員 notify + set=true |
| 4-6 | lock.jl | `base/lock.jl` | **505-510行目**: `Semaphore` 構造体。sem_size, curr_cnt, cond_wait |
| 4-7 | lock.jl | `base/lock.jl` | **518-529行目**: `acquire(s)` - lock → cnt >= max なら wait → cnt++ |
| 4-8 | lock.jl | `base/lock.jl` | **601-611行目**: `release(s)` - lock → cnt-- → notify |

#### Step 5: OncePerProcess / OncePerThread / OncePerTask を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | lock.jl | `base/lock.jl` | **729-776行目**: `OncePerProcess` - state 確認 → ロック取得 → 初期化 → state = PerStateHasrun |
| 5-2 | lock.jl | `base/lock.jl` | **842-947行目**: `OncePerThread` - AtomicMemory でスレッドごとの状態管理 → PerThreadLock で排他 |
| 5-3 | lock.jl | `base/lock.jl` | **980-991行目**: `OncePerTask` - task_local_storage + get! で遅延初期化 |

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

```
lock(rl::ReentrantLock)
    ├─ trylock(rl)
    │      ├─ rl.locked_by === ct ? reentrancy_cnt++ : _trylock
    │      └─ _trylock: GC.disable → CAS(havelock) → locked_by = ct
    └─ slowlock(rl)  [trylock 失敗時]
           ├─ CAS(havelock) 再試行
           ├─ スピン (MAX_SPIN_ITERS=40)
           ├─ PARKED_BIT 設定
           └─ wait_no_relock(cond_wait)

unlock(rl::ReentrantLock)
    └─ _unlock(rl)
           ├─ reentrancy_cnt--
           ├─ locked_by = nothing
           ├─ CAS(havelock → 0)
           └─ [PARKED_BIT] notifywaiters
                  ├─ lock(cond_wait)
                  ├─ notify(cond_wait, all=false)
                  └─ havelock = PARKED_BIT or 0

lock(l::SpinLock)
    └─ while !trylock(l)
           ├─ jl_cpu_suspend
           └─ jl_gc_safepoint

wait(c::GenericCondition)
    ├─ _wait2(c, ct)       ... waitq に登録
    ├─ unlockall(c.lock)   ... ロック解放
    ├─ wait()              ... suspend
    └─ relockall(c.lock)   ... ロック再取得

acquire(s::Semaphore)
    ├─ lock(s.cond_wait)
    ├─ while cnt >= max: wait(s.cond_wait)
    ├─ cnt++
    └─ unlock(s.cond_wait)
```

### データフロー図

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

(なし) ──────────▶ ReentrantLock()                   ──────▶ ロックオブジェクト
lock   ──────────▶ trylock → CAS / slowlock          ──────▶ ロック取得
unlock ──────────▶ reentrancy-- → release             ──────▶ ロック解放

(なし) ──────────▶ Threads.Condition()                ──────▶ 条件変数
wait   ──────────▶ waitq 登録 → unlock → suspend      ──────▶ 値 (notify の引数)
notify ──────────▶ waitq → schedule                   ──────▶ 起床タスク数

sem_size ────────▶ Semaphore(n)                       ──────▶ セマフォ
acquire ─────────▶ cnt チェック → wait/cnt++           ──────▶ 取得完了
release ─────────▶ cnt-- → notify                     ──────▶ 解放完了

autoreset ───────▶ Event(autoreset)                   ──────▶ イベント
wait    ─────────▶ set チェック → wait                  ──────▶ イベント発火
notify  ─────────▶ set=true → notify                   ──────▶ 通知完了

init    ─────────▶ OncePerProcess{T}(init)            ──────▶ callable
()      ─────────▶ state チェック → init()              ──────▶ T 型の値
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| lock.jl | `base/lock.jl` | ソース | ReentrantLock, Semaphore, Event, Lockable, OncePerProcess/Thread/Task, @lock マクロ |
| locks-mt.jl | `base/locks-mt.jl` | ソース | SpinLock, PaddedSpinLock |
| condition.jl | `base/condition.jl` | ソース | AbstractLock, GenericCondition, Condition, wait, notify |
| task.jl | `base/task.jl` | ソース | Task（同期プリミティブの利用者） |
| channels.jl | `base/channels.jl` | ソース | Channel（Threads.Condition を内部使用） |
