mcpField
A
axiom-synchronization

axiom-synchronization

github.com/charleswiltgen開発1.2k168 回インストール今日更新
リポジトリ

axiom-synchronization について

Swift 開発者向けに、低レベルのスレッド同期プリミティブを選ぶためのリファレンスです。Mutex、OSAllocatedUnfairLock、Atomic の使い方と、Swift の並行処理と組み合わせる際の注意点を扱います。

主な機能

  1. 01Mutex、ロック、Atomic の用途比較
  2. 02クロージャ経由で保護状態にアクセス
  3. 03スレッドセーフなカウンターと値ラッパーの構築
  4. 04iOS 16 向けのロック代替手段
  5. 05await 中のロック保持やスレッドプールのブロックを回避

活用シーン

  1. 01頻繁に参照されるプロパティの保護
  2. 02ロックフリーのカウンターやフラグの実装
  3. 03Actor 内で同期的に高速読み取り

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-synchronization

インストール後、Claude Code で/axiom-synchronizationを入力して読み込まれたか確認します。

詳細

Mutex と同期 — スレッドセーフなプリミティブ

アクターでは遅すぎる、またはオーバーヘッドが大きすぎる場合に使う、低レベルの同期プリミティブです。

Mutex と Actor の使い分け

必要なもの使用するもの理由
マイクロ秒単位の処理Mutex非同期ホップのオーバーヘッドがありません
単一のプロパティの保護Mutexよりシンプルで高速です
複雑な非同期ワークフローActorサスペンションを適切に処理できます
サスペンションポイントが必要ActorMutex ではサスペンドできません
モジュール間で共有MutexSendable で、await が不要です
高頻度のカウンターAtomicロックフリーのパフォーマンス

API リファレンス

Mutex(iOS 18 以降/Swift 6)
import Synchronization

let mutex = Mutex<Int>(0)

// Read
let value = mutex.withLock { $0 }

// Write
mutex.withLock { $0 += 1 }

// Non-blocking attempt
if let value = mutex.withLockIfAvailable({ $0 }) {
    // Got the lock
}

プロパティ:

  • 保護対象の値をジェネリックに扱います
  • Sendable — 並行処理の境界を越えて安全に共有できます
  • クロージャーベースのアクセスのみ(lock/unlock メソッドはありません)
OSAllocatedUnfairLock(iOS 16 以降)
import os

let lock = OSAllocatedUnfairLock(initialState: 0)

// Closure-based (recommended)
lock.withLock { state in
    state += 1
}

// Traditional (same-thread only)
lock.lock()
defer { lock.unlock() }
// access protected state

プロパティ:

  • ヒープに割り当てられ、メモリーアドレスが安定しています
  • 非再帰的(同じスレッドから再ロックできません)
  • Sendable
Atomic 型(iOS 18 以降)
import Synchronization

let counter = Atomic<Int>(0)

// Atomic increment
counter.wrappingAdd(1, ordering: .relaxed)

// Compare-and-swap
let (exchanged, original) = counter.compareExchange(
    expected: 0,
    desired: 42,
    ordering: .acquiringAndReleasing
)

パターン

パターン 1:スレッドセーフなカウンター
final class Counter: Sendable {
    private let mutex = Mutex<Int>(0)

    var value: Int { mutex.withLock { $0 } }
    func increment() { mutex.withLock { $0 += 1 } }
}
パターン 2:Sendable ラッパー
final class ThreadSafeValue<T: Sendable>: @unchecked Sendable {
    private let mutex: Mutex<T>

    init(_ value: T) { mutex = Mutex(value) }

    var value: T {
        get { mutex.withLock { $0 } }
        set { mutex.withLock { $0 = newValue } }
    }
}
パターン 3:Actor 内での高速な同期アクセス
actor ImageCache {
    // Mutex for fast sync reads without actor hop
    private let mutex = Mutex<[URL: Data]>([:])

    nonisolated func cachedSync(_ url: URL) -> Data? {
        mutex.withLock { $0[url] }
    }

    func cacheAsync(_ url: URL, data: Data) {
        mutex.withLock { $0[url] = data }
    }
}
パターン 4:Atomic を使ったロックフリーのカウンター
final class FastCounter: Sendable {
    private let _value = Atomic<Int>(0)

    var value: Int { _value.load(ordering: .relaxed) }

    func increment() {
        _value.wrappingAdd(1, ordering: .relaxed)
    }
}
パターン 5:iOS 16 向けのフォールバック
#if compiler(>=6.0)
import Synchronization
typealias Lock<T> = Mutex<T>
#else
import os
// Use OSAllocatedUnfairLock for iOS 16-17
#endif

注意:Swift の並行処理との混在

await をまたいでロックを保持しないでください
// ❌ DEADLOCK RISK
mutex.withLock {
    await someAsyncWork()  // Task suspends while holding lock!
}

// ✅ SAFE: Release before await
let value = mutex.withLock { $0 }
let result = await process(value)
mutex.withLock { $0 = result }
セマフォ/RWLock が安全ではない理由

Swift の協調スレッドプールには、限られた数のスレッドしかありません。ブロッキングプリミティブを使うと、プールのスレッドを使い果たします。

// ❌ DANGEROUS: Blocks cooperative thread
let semaphore = DispatchSemaphore(value: 0)
Task {
    semaphore.wait()  // Thread blocked, can't run other tasks!
}

// ✅ Use async continuation instead
await withCheckedContinuation { continuation in
    // Non-blocking callback
    callback { continuation.resume() }
}
os_unfair_lock の危険性

Swift で os_unfair_lock を直接使用しないでください — メモリー上で移動される可能性があります。

// ❌ UNDEFINED BEHAVIOR: Lock may move
var lock = os_unfair_lock()
os_unfair_lock_lock(&lock)  // Address may be invalid

// ✅ Use OSAllocatedUnfairLock (heap-allocated, stable address)
let lock = OSAllocatedUnfairLock()

判断ツリー

Need synchronization?
├─ Lock-free operation needed?
│  └─ Simple counter/flag? → Atomic
│  └─ Complex state? → Mutex
├─ iOS 18+ available?
│  └─ Yes → Mutex
│  └─ No, iOS 16+? → OSAllocatedUnfairLock
├─ Need suspension points?
│  └─ Yes → Actor (not lock)
├─ Cross-await access?
│  └─ Yes → Actor (not lock)
└─ Performance-critical hot path?
   └─ Yes → Mutex/Atomic (not actor)

よくある間違い

間違い 1:非同期の調整に Lock を使用する
// ❌ Locks don't work with async
let mutex = Mutex<Bool>(false)
Task {
    await someWork()
    mutex.withLock { $0 = true }  // Race condition still possible
}

// ✅ Use actor or async state
actor AsyncState {
    var isComplete = false
    func complete() { isComplete = true }
}
間違い 2:再帰ロックを試みる
// ❌ Deadlock — OSAllocatedUnfairLock is non-recursive
lock.withLock {
    doWork()  // If doWork() also calls withLock → deadlock
}

// ✅ Refactor to avoid nested locking
let data = lock.withLock { $0.copy() }
doWork(with: data)
間違い 3:異なるロック方式を混在させる
// ❌ Don't mix lock/unlock with withLock
lock.lock()
lock.withLock { /* ... */ }  // Deadlock!
lock.unlock()

// ✅ Pick one style
lock.withLock { /* all work here */ }

メモリーオーダリングのクイックリファレンス

オーダリング読み取り書き込み使用例
.relaxedはいはいカウンター、依存関係なし
.acquiringはい-依存する操作の前に読み込む
.releasing-はい依存する操作の後に書き込む
.acquiringAndReleasingはいはい読み取り・変更・書き込み
.sequentiallyConsistentはいはい最も強い保証

デフォルトの選択肢:カウンターには .relaxed、読み取り・変更・書き込みには .acquiringAndReleasing を使用します。

リソース

ドキュメント:/synchronization、/synchronization/mutex、/os/osallocatedunfairlock

Swift Evolution:SE-0433

スキル:axiom-swift-concurrency、axiom-swift-performance

コメント