---
title: "Conquer Data Races with Swift Actors"
slug: "conquer-data-races-with-swift-actors"
date: "2023-02-07T19:08:18.000Z"
description: "Unleash the power of Swift concurrency with Actors! Get all the information you need in this comprehensive article"
---

# Conquer Data Races with Swift Actors

Mobile development is close to impossible without concurrent code. While executing tasks concurrently generally speeds up your app, it also introduces a lot of challenges to overcome. And one of them is a data race.

## Data Races And When They Happen

Try to find a problem in the code below

```swift
import Foundation

var counter = 0
let queue = DispatchQueue.global()

for _ in 1...100500 {
    queue.async {
        counter += 1
    }
}

queue.sync(flags: .barrier) {
    // Synchronous barrier to wait untill all
    // async tasks are finished
    print("Final value: \(counter)")
}
```

This does not output `100500` as desired

`Final value: 100490`

Let me run the same code one more time.

`Final value: 100486`

Voilà

As you see, the same code produces different results. In this case, we deal with a **data race.**

💡

Data races occur when multiple threads access a shared resource without protections, leading to undefined behaviour

In the code above, asynchronous tasks capture `counter` and modify it simultaneously. This leads to undefined behaviour.

#### What's under the hood?

The reasoning behind such behaviour is in assembly operations. Before incrementing the value, it is loaded from RAM into the processor's register. At the same time, other threads can increment the value and save it back to RAM. But the thread that saved value from memory to register will not know about it and will continue to work with the old value, eventually overwriting the updated value in RAM

## Non-Actor Solutions

Before the introduction of actors, several solutions to the problem were used.

### Serial Queue

We can create a dedicated queue that will be used during all accesses to the counter. Internally, tasks execute serially, so no data races occur.

```swift
import Foundation

var counter = 0
let queue = DispatchQueue.global()

// Serial queue
let counterAccessQueue = DispatchQueue(label: "CounterAccessQueue")

for _ in 1...100500 {
	queue.async {
		counterAccessQueue.sync { counter += 1 }
	}
}

queue.sync(flags: .barrier) {
	counterAccessQueue.sync { print("Final value: \(counter)") }
}
```

### Concurrent Queue With Barrier

It's possible to use sync with barrier parameter to modify value even in concurrent queue. Basically, the barrier waits until all previous tasks are completed, then it executes code synchronously, and after that queue continues to operate as usual.

In the current example, it basically transforms concurrent queue to serial, but still, it's a different approach.

```swift
import Foundation

var counter = 0
let queue = DispatchQueue.global()

for _ in 1...100500 {
	queue.sync(flags: .barrier) {
		counter += 1
	}
}

queue.sync {
	print("Final value: \(counter)")
}
```

Subscribe and don't miss posts!

## Actors Model

The actor model is an architecturally different approach. Consider actors as classes with additional restrictions. Ideologically, code inside actors **cannot be executed concurrently**, therefore actors can safely modify their state.

> In the world of chaos (concurrent) consider actors as a safe space

Also, other instances cannot modify the actor's state from the outside. Thus, ensuring the safety of accesses.

💥

All in all, actors let you safely share information between concurrent contexts

## Using Actors in Swift

Luckily, we do not need to implement the actor model ourselves. Starting from **Swift 5.7**, actors are available as part of Swift concurrency.

Actors are defined with `actor` keyword.

```swift
actor Counter {
	private(set) var counter = 0
    
	func increment() {
		counter += 1
	}
}
```

💡

Like classes, actors are ****reference types****

Generally, all access to actors may be suspended and require `await` keyword.

💥

If you're unfamiliar with Swift concurrency, check out my quick guide!

[

Quick Guide to Async Await in Swift | Alex Dremov

Everything you need to know about new Swift asynchronous features. Async await, main actor, task, async get, and possible use cases — all covered.

![](https://alexdremov.me/assets/icons/apple-touch-icon.png?v=eef9b14b42)Alex DremovAlex Dremov

![](https://alexdremov.me/content/images/2022/04/slide_17.jpg)

](https://alexdremov.me/quick-guide-to-async-await-in-swift/)

Now, according to the defined model, an actor represents an isolated state. Therefore, we cannot directly execute code inside the actor or change its state because some other task can already be changing the actor's state.

We want to mitigate data races!

```swift
let counter = Counter()
let queue = DispatchQueue.global()

// Used only to wait for all tasks to complete
let group = DispatchGroup()

for _ in 1...100500 {
    group.enter()
    
    queue.async {
    	// async calls can be executed only in
        // appropriate concurrent environment, so
        // we spawn a new task
        Task.detached {
            await counter.increment()
            group.leave()
        }
    }
}

group.wait()
Task {
    print("Final value: \(await counter.counter)")
}
```

As you see, all calls to methods of `Counter` and even to its properties are asynchronous and marked with `await` keyword.

💡

Notice that `await` is not needed inside the actor's method. That's because the actor's methods are already inside an isolated state

### Nonisolated Members

All members of actors are by default isolated. Actors also can have non-isolated members. Access to them is the same as if actor was a regular class. Notice, though, that nonisolated methods cannot directly access isolated members.

😡

Stored non-constant properties cannot be `nonisolated`

💡

Constant properties ( `let` ) are `nonisolated` by default, as they cannot provoke a data race

```swift
actor Counter {
    let id = UUID()
    private(set) var counter: Int = 0
    
    private nonisolated var description: String {
        "Counter"
    }
    
    func increment() {
        counter += 1
    }
    
    nonisolated func getDescription() -> String {
        return description
    }
}

...

print(counter.getDescription()) // no await
print(counter.id) // no await
```

## Difference to Locks

One may ask

> How's it different from taking a lock before executing code inside an actor and releasing a lock on an exit?

The difference is noticeable if actor itself runs asynchronous operations inside it. For example, if it messages another actor.

Take a look

```swift
actor Ping {
    let pong = Pong()
    
    func run() async {
        print("ping!")
        await pong.run() // Suspension point
        
        // While pong.run() is waited, other tasks
        // can enter this actor
    }
}

actor Pong {
    func run() async {
        try! await Task.sleep(for: .seconds(1)) // sleeping a bit
        print("pong!")
    }
}

let ping = Ping()
Task {
    await ping.run()
}

Task{
    await ping.run()
}
```

This code outputs

```
ping!
ping!
pong!
pong!
```

Notice that another actor is also called using `await` keyword. I marked this place as a suspension point. The current task is suspended while waiting for an asynchronous task, **so the actor is free for entrance again.**

That's the core difference to a simple mutex or lock, and it is called **Actor Reentrancy**. Some consider this a problem. However, it is an awesome optimization at expense of complicating code a bit.

💥

Mind about actor reentrancy! It is incorrect to make assumptions about an actor's state after an `await` call inside an actor

```swift
actor Door {
    private var _open = false
    
    func open() async {
        _open = true
        
        await someTask() // Suspension point
        
        // Mistake! Door could have been closed
        // while someTask was executing
        print("Door is open")
    }
    
    func close() {
        _open = false
    }
}
```

Luckily, suspension points are all marked with `await` keyword, so it is easy to keep track of them

## Final Notes

Actors are a great solution to data races. They nicely integrate into Swift concurrency. Keep in mind, though, that actor reentrancy must be taken into account to avoid incorrect state assumptions.

## References

[

Actor | Apple Developer Documentation

Common protocol to which all actors conform.

![](https://alexdremov.me/content/images/icon/favicon-3c79dfa1-48ef-46a3-bbde-64d063cb3300.ico)Apple Developer Documentation

![](https://alexdremov.me/content/images/thumbnail/developer-og-30d7c182-5491-446c-9161-b8a09d50db54.jpg)

](https://developer.apple.com/documentation/swift/actor)

[

Documentation

![](https://alexdremov.me/content/images/icon/favicon-c4b4e562-5389-4d30-a24c-d2801072f84e.ico)Swift.org



](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/)