4

Let's say I want to write the value type properties of a class instance in one thread, while reading the same from another thread -- see example code below. What is the worst that can happen?

Obviously there are no guarantees for the internal consistency of x at the time of reading, the read thread might print 0, true, bar, [1, 2], or 1, true, foo, [1, 2], etc. Let's assume that's not a concern.

Are the property assignments themselves atomic? Or is it possible for the writer thread to start but not quite finish the assignment of, say, x.i = 2, before the reader thread prints the value of x.i? Does the answer depend on the kind of value type (Int, Bool, String, Array, etc.)?

If the assignments are atomic, where is this documented?

If they are not atomic, can this cause a crash, or the printing of a value that wasn't actually assigned (in the case of x.i, not one of 0, 1 or 2)? Is there a way to write a test to demonstrate this?

class X {
    var i: Int = 0
    var b: Bool = false
    var s: String = ""
    var a: [Int] = []
}

func write(x: X) {
    x.i = 1
    x.b = true
    x.s = "foo"
    x.a = [1]
    x.i = 2
    x.b = false
    x.s = "bar"
    x.a = [1, 2]
}

func read(x: X) {
    print(x.i)
    print(x.b)
    print(x.s)
    print(x.a)
}

var x = X()

let queue = DispatchQueue.global()

queue.async {
    write(x: x)
}
queue.async {
    read(x: x)
}

Edit: Reflecting on the comments, I wanted to add that I am primarily interested in Bool and String values, and the iOS platform(s) -- and I ran a test with a few million read/write cycles on several devices without triggering any kind of error. So even knowing that Swift doesn't provide any guarantees, I still wonder if one could reasonably rely on the platform itself to do so. I changed the title to reflect this.

Zsombor
  • 141
  • 7
  • 3
    IIRC, Swift doesn't go out of its way to add any additional locking, so atomicity comes down to the architecture. In the case of x86, word-sized operands (including pointers) being `mov`ed are guaranteed to be atomic. So if you were assigning `0x0000` to `0x1234`, there's no chance a separate thread might read a `0x1200` or `0x0034` – Alexander Dec 17 '17 at 17:29
  • 2
    You should absolutely _not_ assume that property assignment is thread-safe in Swift. The way to avoid issues is to write thread-safe code. – matt Dec 17 '17 at 17:32
  • 4
    The use of global queue like this offers no synchronization (other than what the architecture provides, which is imprudent to rely upon). However, you can use reader-writer pattern by making your queue a custom concurrent queue (not global queue), you can then do reads with `queue.sync` and writes with `queue.async(flags: .barrier)`. That’s an efficient way of synchronizing access. As Alexander said, some architectures make this unnecessary for certain types, but if you want to future-proof your code, it’s best to just synchronize your access (e.g. with this reader-writer pattern). – Rob Dec 17 '17 at 19:05
  • @rob Thanks for this comment – its enough for an answer for me! Do you know if there's a statement from an authoritative party (Apple, say), that property reads and writes provide no atomicity guarantee? – Benjohn Dec 02 '18 at 12:13
  • 2
    @Benjohn - IMHO, you should be asking the opposite, namely where in the Apple documentation does it actually guarantee atomicity (aside from what is provided by the architecture). I know that [SE-0030](https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md#synchronized-property-access) contemplates atomic properties, but this has been deferred AFAIK. – Rob Dec 04 '18 at 13:28
  • By the way, this may be a duplicate of https://stackoverflow.com/questions/24157834/are-swift-variables-atomic – Rob Dec 04 '18 at 13:29
  • @Rob That seems like a reasonable position… I suppose one could take the position that without further comment, _nothing_ is "thread" safe – ie, even constant values aren't safe for reading from another thread. Swift doesn't seem to take that stance – presumably if it did, it would be a "shared nothing" language and concurrency primitives would be needed for any shared resource? I'm not really clear what its current stance is? – Benjohn Dec 04 '18 at 23:09
  • 1
    I was a little surprised that Apple hasn’t addressed this in the Swift as the language is used in multi-threaded environments all the time. But as recently as WWDC 2016, they talked about pros and cons of unfair locks vs GCD for synchronization (https://developer.apple.com/videos/play/wwdc2016/720/?time=1160) of properties, with definitely no native language constructs contemplated. Or see https://developer.apple.com/videos/play/wwdc2016/412/?time=840 – Rob Dec 05 '18 at 01:08

0 Answers0