axiom-synchronization

安装量: 117
排名: #7320

安装

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

Mutex & Synchronization — Thread-Safe Primitives

Low-level synchronization primitives for when actors are too slow or heavyweight.

When to Use Mutex vs Actor Need Use Reason Microsecond operations Mutex No async hop overhead Protect single property Mutex Simpler, faster Complex async workflows Actor Proper suspension handling Suspension points needed Actor Mutex can't suspend Shared across modules Mutex Sendable, no await needed High-frequency counters Atomic Lock-free performance API Reference Mutex (iOS 18+ / Swift 6) import Synchronization

let mutex = Mutex(0)

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

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

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

Properties:

Generic over protected value Sendable — safe to share across concurrency boundaries Closure-based access only (no lock/unlock methods) 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

Properties:

Heap-allocated, stable memory address Non-recursive (can't re-lock from same thread) Sendable Atomic Types (iOS 18+) import Synchronization

let counter = Atomic(0)

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

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

Patterns Pattern 1: Thread-Safe Counter final class Counter: Sendable { private let mutex = Mutex(0)

var value: Int { mutex.withLock { $0 } }
func increment() { mutex.withLock { $0 += 1 } }

}

Pattern 2: Sendable Wrapper final class ThreadSafeValue: @unchecked Sendable { private let mutex: Mutex

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

var value: T {
    get { mutex.withLock { $0 } }
    set { mutex.withLock { $0 = newValue } }
}

}

Pattern 3: Fast Sync Access in 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 }
}

}

Pattern 4: Lock-Free Counter with Atomic final class FastCounter: Sendable { private let _value = Atomic(0)

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

func increment() {
    _value.wrappingAdd(1, ordering: .relaxed)
}

}

Pattern 5: iOS 16 Fallback

if compiler(>=6.0)

import Synchronization typealias Lock = Mutex

else

import os // Use OSAllocatedUnfairLock for iOS 16-17

endif

Danger: Mixing with Swift Concurrency Never Hold Locks Across 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 }

Why Semaphores/RWLocks Are Unsafe

Swift's cooperative thread pool has limited threads. Blocking primitives exhaust the pool:

// ❌ 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 Danger

Never use os_unfair_lock directly in Swift — it can be moved in memory:

// ❌ 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()

Decision Tree 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)

Common Mistakes Mistake 1: Using Lock for Async Coordination // ❌ Locks don't work with async let mutex = Mutex(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 } }

Mistake 2: Recursive Locking Attempt // ❌ 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)

Mistake 3: Mixing Lock Styles // ❌ Don't mix lock/unlock with withLock lock.lock() lock.withLock { / ... / } // Deadlock! lock.unlock()

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

Memory Ordering Quick Reference Ordering Read Write Use Case .relaxed Yes Yes Counters, no dependencies .acquiring Yes - Load before dependent ops .releasing - Yes Store after dependent ops .acquiringAndReleasing Yes Yes Read-modify-write .sequentiallyConsistent Yes Yes Strongest guarantee

Default choice: .relaxed for counters, .acquiringAndReleasing for read-modify-write.

Resources

Docs: /synchronization, /synchronization/mutex, /os/osallocatedunfairlock

Swift Evolution: SE-0433

Skills: axiom-swift-concurrency, axiom-swift-performance

返回排行榜