Defending mutable state with Mutex in Swift – Donny Wals

Defending mutable state with Mutex in Swift – Donny Wals


When you begin utilizing Swift Concurrency, actors will basically develop into your normal alternative for safeguarding mutable state. Nonetheless, introducing actors additionally tends to introduce extra concurrency than you supposed which might result in extra complicated code, and a a lot more durable time transitioning to Swift 6 in the long term.

While you work together with state that’s protected by an actor, it’s a must to to take action asynchronously. The result’s that you just’re writing asynchronous code in locations the place you may by no means have supposed to introduce concurrency in any respect.

One solution to resolve that’s to annotate your for example view mannequin with the @MainActor annotation. This makes certain that every one your code runs on the primary actor, which signifies that it is thread-safe by default, and it additionally makes certain you can safely work together together with your mutable state.

That mentioned, this may not be what you are on the lookout for. You may need to have code that does not run on the primary actor, that is not remoted by world actors or any actor in any respect, however you simply need to have an old style thread-safe property.

Traditionally, there are a number of methods during which we will synchronize entry to properties. We used to make use of Dispatch Queues, for instance, when GCD was the usual for concurrency on Apple Platforms.

Just lately, the Swift staff added one thing known as a Mutex to Swift. With mutexes, we’ve an alternative choice to actors for safeguarding our mutable state. I say different, nevertheless it’s probably not true. Actors have a really particular position in that they shield our mutable state for a concurrent setting the place we would like code to be asynchronous. Mutexes, then again, are actually helpful after we don’t need our code to be asynchronous and when the operation we’re synchronizing is fast (like assigning to a property).

On this publish, we’ll discover how one can use Mutex, when it is helpful, and the way you select between a Mutex or an actor.

Mutex utilization defined

A Mutex is used to guard state from concurrent entry. In most apps, there shall be a handful of objects that is perhaps accessed concurrently. For instance, a token supplier, an picture cache, and different networking-adjacent objects are sometimes accessed concurrently.

On this publish, I’ll use a quite simple Counter object to verify we don’t get misplaced in complicated particulars and specifics that don’t affect or change how we use a Mutex.

While you increment or decrement a counter, that’s a fast operation. And in a codebase the place. the counter is obtainable in a number of duties on the similar time, we would like these increment and decrement operations to be protected and free from knowledge races.

Wrapping your counter in an actor is smart from a concept viewpoint as a result of we would like the counter to be shielded from concurrent accesses. Nonetheless, after we do that, we make each interplay with our actor asynchronous.

To considerably stop this, we may constrain the counter to the primary actor, however that signifies that we’re all the time going to should be on the primary actor to work together with our counter. We would not all the time be on the identical actor after we work together with our counter, so we’d nonetheless should await interactions in these conditions, and that is not perfect.

With the intention to create a synchronous API that can be thread-safe, we may fall again to GCD and have a serial DispatchQueue.

Alternatively, we will use a Mutex.

A Mutex is used to wrap a bit of state and it ensures that there is unique entry to that state. A Mutex makes use of a lock underneath the hood and it comes with handy strategies to ensure that we purchase and launch our lock rapidly and accurately.

After we attempt to work together with the Mutex‘ state, we’ve to attend for the lock to develop into accessible. That is just like how an actor would work with the important thing distinction being that ready for a Mutex is a blocking operation (which is why we should always solely use it for fast and environment friendly operations).

This is what interacting with a Mutex seems to be like:

class Counter {
    non-public let mutex = Mutex(0)

    func increment() {
        mutex.withLock { depend in
            depend += 1
        }
    }

    func decrement() {
        mutex.withLock { depend in
            depend -= 1
        }
    }
}

Our increment and decrement features each purchase the Mutex, and mutate the depend that’s handed to withLock.

Our Mutex is outlined by calling the Mutex initializer and passing it our preliminary state. On this case, we cross it 0 as a result of that’s the beginning worth for our counter.

On this instance, I’ve outlined two features that safely mutate the Mutex‘ state. Now let’s see how we will get the Mutex‘ worth:

var depend: Int {
    return mutex.withLock { depend in
        return depend
    }
}

Discover that studying the Mutex worth can be completed withLock. The important thing distinction with increment and decrement right here is that as a substitute of mutating depend, I simply return it.

It’s completely important that we hold our operations within withLock quick. We don’t need to maintain the lock for any longer than we completely should as a result of any threads which might be ready for our lock or blocked whereas we maintain the lock.

We are able to broaden our instance a little bit bit by including a get and set to our depend. This may enable customers of our Counter to work together with depend prefer it’s a traditional property whereas we nonetheless have data-race safety underneath the hood:

var depend: Int {
    get {
        return mutex.withLock { depend in
            return depend
        }
    }

    set {
        mutex.withLock { depend in
            depend = newValue
        }
    }
}

We are able to now use our Counter as follows:

let counter = Counter()

counter.depend = 10
print(counter.depend)

That’s fairly handy, proper?

Whereas we now have a sort that is freed from data-races, utilizing it in a context the place there are a number of isolation contexts is a little bit of a difficulty after we opt-in to Swift 6 since our Counter doesn’t conform to the Sendable protocol.

The great factor about Mutex and sendability is that mutexes are outlined as being Sendable in Swift itself. Because of this we will replace our Counter to be Sendable fairly simply, and with no need to make use of @unchecked Sendable!

remaining class Counter: Sendable {
    non-public let mutex = Mutex(0)

    // ....
}

At this level, we’ve a fairly good setup; our Counter is Sendable, it’s freed from data-races, and it has a completely synchronous API!

After we try to use our Counter to drive a SwiftUI view by making it @Observable, this get a little bit tough:

struct ContentView: View {
    @State non-public var counter = Counter()

    var physique: some View {
        VStack {
            Textual content("(counter.depend)")

            Button("Increment") {
                counter.increment()
            }

            Button("Decrement") {
                counter.decrement()
            }
        }
        .padding()
    }
}

@Observable
remaining class Counter: Sendable {
    non-public let mutex = Mutex(0)

    var depend: Int {
        get {
            return mutex.withLock { depend in
                return depend
            }
        }

        set {
            mutex.withLock { depend in
                depend = newValue
            }
        }
    }
}

The code above will compile however the view received’t ever replace. That’s as a result of our computed property depend relies on state that’s not explicitly altering. The Mutex will change the worth it protects however that doesn’t change the Mutex itself.

In different phrases, we’re not mutating any knowledge in a approach that @Observable can “see”.

To make our computed property work @Observable, we have to manually inform Observable after we’re accessing or mutating (on this case, the depend keypath). This is what that appears like:

var depend: Int {
    get {
        self.entry(keyPath: .depend)
        return mutex.withLock { depend in
            return depend
        }
    }

    set {
        self.withMutation(keyPath: .depend) {
            mutex.withLock { depend in
                depend = newValue
            }
        }
    }
}

By calling the entry and withMutation strategies that the @Observable macro provides to our Counter, we will inform the framework after we’re accessing and mutating state. This may tie into our Observable’s common state monitoring and it’ll enable our views to replace after we change our depend property.

Mutex or actor? How one can determine?

Selecting between a mutex and an actor just isn’t all the time trivial or apparent. Actors are actually good in concurrent environments when you have already got a complete bunch of asynchronous code. When you do not need to introduce async code, or while you’re solely defending one or two properties, you are in all probability within the territory the place a mutex makes extra sense as a result of the mutex won’t pressure you to put in writing asynchronous code wherever.

I may faux that it is a trivial choice and it is best to all the time use mutexes for easy operations like our counter and actors solely make sense while you need to have a complete bunch of stuff working asynchronously, however the choice often is not that simple.

When it comes to efficiency, actors and mutexes do not range that a lot, so there’s not an enormous apparent efficiency profit that ought to make you lean in a single course or the opposite.

Ultimately, your alternative needs to be based mostly round comfort, consistency, and intent. For those who’re discovering your self having to introduce a ton of async code simply to make use of an actor, you are in all probability higher off utilizing a Mutex.

Actors needs to be thought of an asynchronous software that ought to solely be utilized in locations the place you’re deliberately introducing and utilizing concurrency. They’re additionally extremely helpful while you’re making an attempt to wrap longer-running operations in a approach that makes them thread-safe. Actors don’t block execution which signifies that you’re fully advantageous with having “slower” code on an actor.

When unsure, I wish to strive each for a bit after which I stick to the choice that’s most handy to work with (and infrequently that’s the Mutex…).

In Abstract

On this publish, you’ve got discovered about mutexes and the way you should use them to guard mutable state. I confirmed you the way they’re used, once they’re helpful, and the way a Mutex compares to an actor.

You additionally discovered a little bit bit about how one can select between an actor or a property that is protected by a mutex.

Making a alternative between an actor or a Mutex is, in my view, not all the time straightforward however experimenting with each and seeing which model of your code comes out simpler to work with is an effective begin while you’re making an attempt to determine between a Mutex and an actor.

Leave a Reply

Your email address will not be published. Required fields are marked *