Locks don't generally break forward progress, and modern locks APIs, like Mutex, ensure the lock is minimally held by scoping access with a closure rather than explicit lock and unlock calls. Locks are only dangerous if you lock on one thread and unlock on another. My usual pattern is to put any state I need to be thread-safe in a struct stored within a lock container like Mutex.
struct MutableState {
var one = ""
var two = 0
}
let state = Mutex(MutableState())
state.withLock {
$0.one = "a"
}
That way you ensure minimal lock time and update all of your state atomically, as withLock modifies the value with inout. This basic pattern can serve both sync and async APIs without the need for actors.
Personally, I only ever use actors around state that either never needs sync access (since doing so is awkward) or is itself synchronous but should be interacted with asynchronously, like the keychain.