Is Array thread-safe, and how do you make shared state safe?

2.7.5 · L2 · Interview

Predict first

Is count += 1 safe if count is a property wrapped in an atomic wrapper, called from two threads?

@Atomic var count = 0
// two threads each run: count += 1
  • Yes, the wrapper makes it atomic
  • No, read-modify-write can interleave

Mental model. Two people editing one sheet of paper at once: each is fine alone, but their edits can tear the page.

No. Several threads may read an array at the same moment, but any write that overlaps another read or write is a data race: undefined behaviour, in practice a crash or corrupted data. Copy-on-write does not help, because the race is on the variable holding the array, not on the buffer.

Make the access serial: guard every read and write with one lock, funnel them through a serial queue, put the state in an actor, or wrap it in a Mutex. A “thread-safe property wrapper” makes a single read or write atomic, but not compound operations: count += 1 is read, add, write, and two threads can interleave. The check-and-change must happen inside one critical section. Thread Sanitizer finds many races, and Swift 6 strict concurrency rejects whole classes of them at compile time.

The wrapper protects each individual get and set. The increment is a get, an add and a set, and another thread can slip in between them.

Common traps

  • Atomic wrappers for read-modify-write. @Atomic var n; n += 1 still loses updates.
  • Locking only the writes. Reads that race with writes are just as undefined.

They might follow up with

  • How do you find a race? Run with Thread Sanitizer, and turn on strict concurrency checking.
  • Why is a let array safe to share? Immutable data cannot race. That is why value types and Sendable help.

Related questions

  • 2.7.3 NSLock, NSRecursiveLock, os_unfair_lock, Mutex, serial queue: how do you choose?
  • 2.7.1 What is NSCache, and how is it different from a Dictionary?
  • 1.10.2 What does Sendable mean for a Swift type you write?

← Why can’t you hold a lock across an await, and what does the compiler…