axiom-synchronization

charleswiltgen/axiom · updated Apr 8, 2026

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

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

skill.md

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<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
}

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<Int>(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<Int>(0)

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

Pattern 2: Sendable Wrapper

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 } }
    }
}

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<Int>(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<T> = Mutex<T>
#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<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 }
}

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

Discussion

Product Hunt–style comments (not star reviews)
  • No comments yet — start the thread.
general reviews

Ratings

4.672 reviews
  • Shikha Mishra· Dec 28, 2024

    axiom-synchronization reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Harper Harris· Dec 28, 2024

    Registry listing for axiom-synchronization matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Noor Reddy· Dec 28, 2024

    Useful defaults in axiom-synchronization — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Anika Smith· Dec 24, 2024

    axiom-synchronization has been reliable in day-to-day use. Documentation quality is above average for community skills.

  • Xiao Harris· Dec 16, 2024

    I recommend axiom-synchronization for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Ganesh Mohane· Dec 4, 2024

    Solid pick for teams standardizing on skills: axiom-synchronization is focused, and the summary matches what you get after install.

  • Noor Park· Dec 4, 2024

    Solid pick for teams standardizing on skills: axiom-synchronization is focused, and the summary matches what you get after install.

  • Sakshi Patil· Nov 23, 2024

    We added axiom-synchronization from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Sophia Ramirez· Nov 23, 2024

    We added axiom-synchronization from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Daniel Agarwal· Nov 19, 2024

    axiom-synchronization is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

showing 1-10 of 72

1 / 8