11.5 C
Canberra
Tuesday, July 22, 2025

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 meant which might result in extra advanced code, and a a lot tougher time transitioning to Swift 6 in the long term.

Whenever 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 meant to introduce concurrency in any respect.

One approach to resolve that’s to annotate your as an instance view mannequin with the @MainActor annotation. This makes certain that every one your code runs on the primary actor, which implies that it is thread-safe by default, and it additionally makes certain you could safely work together together with your mutable state.

That mentioned, this won’t be what you are searching for. You may wish 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 wish to have an old school thread-safe property.

Traditionally, there are a number of methods by which we are able to 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 group added one thing referred to as a Mutex to Swift. With mutexes, we now have a substitute for actors for safeguarding our mutable state. I say different, nevertheless it’s not likely true. Actors have a really particular position in that they shield our mutable state for a concurrent surroundings the place we would like code to be asynchronous. Mutexes, however, are actually helpful once 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 put up, 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 may be accessed concurrently. For instance, a token supplier, an picture cache, and different networking-adjacent objects are sometimes accessed concurrently.

On this put up, I’ll use a quite simple Counter object to ensure we don’t get misplaced in advanced particulars and specifics that don’t influence or change how we use a Mutex.

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

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

To considerably stop this, we might constrain the counter to the primary actor, however that implies that we’re at all times going to should be on the primary actor to work together with our counter. We would not at all times be on the identical actor once we work together with our counter, so we might nonetheless should await interactions in these conditions, and that is not superb.

In an effort to create a synchronous API that can also be thread-safe, we might fall again to GCD and have a serial DispatchQueue.

Alternatively, we are able to use a Mutex.

A Mutex is used to wrap a chunk of state and it ensures that there is unique entry to that state. A Mutex makes use of a lock beneath the hood and it comes with handy strategies to be sure that we purchase and launch our lock rapidly and appropriately.

Once we attempt to work together with the Mutex‘ state, we now have to attend for the lock to develop into out there. That is much 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 must always solely use it for fast and environment friendly operations).

This is what interacting with a Mutex appears 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 are able to get the Mutex‘ worth:

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

Discover that studying the Mutex worth can also be achieved 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 wish to maintain the lock for any longer than we completely should as a result of any threads which can be ready for our lock or blocked whereas we maintain the lock.

We are able to broaden our instance somewhat bit by including a get and set to our depend. This can permit customers of our Counter to work together with depend prefer it’s a traditional property whereas we nonetheless have data-race safety beneath 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 problem once 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. Which means we are able to replace our Counter to be Sendable fairly simply, and without having to make use of @unchecked Sendable!

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

    // ....
}

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

Once we try to use our Counter to drive a SwiftUI view by making it @Observable, this get somewhat 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
last 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 is predicated 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 information in a approach that @Observable can “see”.

To make our computed property work @Observable, we have to manually inform Observable once 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 are able to inform the framework once we’re accessing and mutating state. This can tie into our Observable’s common state monitoring and it’ll permit our views to replace once we change our depend property.

Mutex or actor? determine?

Selecting between a mutex and an actor just isn’t at all times 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 wish to introduce async code, or once 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 is not going to drive you to put in writing asynchronous code anyplace.

I might faux that it is a trivial determination and it’s best to at all times use mutexes for easy operations like our counter and actors solely make sense once you wish to have a complete bunch of stuff working asynchronously, however the determination often is not that easy.

By way of efficiency, actors and mutexes do not fluctuate 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.

In the long run, your alternative ought to be based mostly round comfort, consistency, and intent. In case you’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 ought to be thought of an asynchronous device that ought to solely be utilized in locations the place you’re deliberately introducing and utilizing concurrency. They’re additionally extremely helpful once you’re making an attempt to wrap longer-running operations in a approach that makes them thread-safe. Actors don’t block execution which implies that you’re utterly tremendous with having “slower” code on an actor.

When unsure, I prefer to attempt each for a bit after which I keep on with the choice that’s most handy to work with (and infrequently that’s the Mutex…).

In Abstract

On this put up, you’ve got discovered about mutexes and the way you need to 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 somewhat 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 at all times simple however experimenting with each and seeing which model of your code comes out simpler to work with is an efficient begin once you’re making an attempt to determine between a Mutex and an actor.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles