Swift Proposal: Synchronous Mutual Exclusion Lock
Not all code may be able (or want) to adopt actors. Reasons for this can be very varied, for example code may have to execute synchronously without any potential for other tasks interleaving with it. Or the
async
effect introduced on methods may prevent legacy code which cannot use Swift Concurrency from interacting with the protected state.[…]
We propose a new type in the Standard Library Synchronization module:
Mutex
. This type will be a wrapper over a platform-specific mutex primitive, along with a user-defined mutable state to protect.[…]
Mutex
will be decorated with the@_staticExclusiveOnly
attribute, meaning you will not be able to declare a variable of typeMutex
asvar
. These are the same restrictions imposed on the recently acceptedAtomic
andAtomicLazyReference
types. […] We do not want to introduce dynamic exclusivity checking when accessing a value ofMutex
as a class stored property for instance.[…]
Mutex
is unconditionallySendable
regardless of the value it's protecting. We can ensure the safetyness of this value due to thetransferring
marked parameters of both the initializer and the closureinout
argument.
On Apple platforms, this is based on os_unfair_lock, which is a bit tricky to directly use from Swift because you have to manage the memory for the lock yourself to prevent Swift from moving it. macOS 13 brought OSAllocatedUnfairLock, which is Apple’s implementation that handles this for you. Mutex
is better still because it doesn’t need to allocate a separate block of memory for the lock.
My experience with Swift Concurrency might be somewhat particular.
[…]
And I’m not sure I’ll replace DispatchQueues with any Swift concurrency constructs any time soon, because a demanding user of SQLite won’t accept to be limited to strictly serialized accesses. SQLite supports concurrent database accesses, including parallel reads and writes. Indeed databases support concurrency in a way that is almost never found in any memory-based synchronization primitive: actors, mutexes, read-write locks are all too limited.
Final nail in the coffin: a demanding SQLite user expects to be able to perform synchronous database accesses at will (i.e. when needed) – just like the regular C API – without any risk of concurrency bugs. There’s currently no Swift concurrency construct that allows both sync and async accesses.
I can’t help feeling Swift’s async, Actor, isolation system is a mistake.
Rather than making the safe thing the default, even if a bit slow, and making a slightly awkward API for a “fast mode” it makes most code think about async isolation most of the time.
Previously:
- Noncopyable Generics Walkthrough
- Swift Atomics 1.1
- @MainActor Not Guaranteed
- Swift 5 Exclusivity Enforcement
- os_unfair_lock