Kodeco Forums

Video Tutorial: Introducing Concurrency Part 9: Thread-safe Resources

Learn how to use dispatch barriers to create thread-safe objects to prevent inconsistent state.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/4034-introducing-concurrency/lessons/10

Hey @samdavies
Thanks for the great videos in this concurrency series :blush: I have a question concerning thread safety if we would take this example from the video to objective-c. If We would declare a property as atomic, would this have the same effect as doing the dispatch barrieres dance like in the tutorial?

I have read a lot of stack overflow questions/answers concerning the atomic keyword. Some answers say that atomic would be equal to do the barrier dance, others say it is not equal… I would like to test it… But how would I tackle that?

Thank you and keep up the good work :blush:

Thanks for the helpful videos. I have learnt a lot.

It seems like all the properties in Swift are non-atomic. This means that even simple types like Int, Bool, Float etc have to be explictly made thread safe, unlike Objective C where we have the option of explicitly specifying if a property is atomic vs non-atomic.

If this is true, should we then create a wrapper around these simple types and use dispatch_barrier in the setter and dispatch_concurrent in the reader for a thread safe version of these simple types?

I have some of my collegues using OSAtomicTestAndSetBarrier() which is considered faster than dispatch_barrier setup which this objc.io article recommends not to use. What are your thoughts on this?

Appreciate your insight into this

Shruti

Hi @reni99

Although declaring an objective-C property as atomic gives you something that looks and feels a bit like thread-safety, it isn’t the same. By making a property atomic you guarantee that when you read it you’ll get a coherent value. That is to say that if one thread is writing to that property at the same time that you’re reading it, your read operation will either get the value from before the write, or after it; it is impossible to get the junk value that represents a partial write.

This is not the same as being thread-safe, where you need the entire “write operation” to be atomic. In the example used in this series, the app first reads the value from the property, before appending a value and then writing it. The race condition occurs when something is able to manipulate the value in memory between the separate read and write operations that form the data update method. You need to make this entire method atomic - which is where the dispatch_barrier dance comes in - allowing you to ensure that nothing can manipulate the property value during the entire data update operation.

I hope that clears it up for you

sam

1 Like

Hi @shruti

I always like to use the highest possible abstraction as possible, and only drop to lower-level tools when required. Grand Central Dispatch is a really easy to understand abstraction, and consequently dispatch_barrier is really simple.

Personally I would go with GCD, and only drop to lower level functions if you really need the performance. GCD is pretty speedy, so is almost certainly not responsible for any speed bottlenecks you might see in an app.

cheers

sam

1 Like

Thank you @samdavies :] You made my day!

So atomic in a way just “protects read operations”. With the barrier dance we can take care of the “write operation” too. so far so good. But there is one question left sticking in my head then… why do we have atomic? I mean, when would we declare a property just as atomic and not make it fully thread safe? I can’t think of a good example right now… do you have any? :wink:

Hey @reni99

Sure - the only reason that we need to force thread safety here is because the current value affects the value we want to write. That is to say that writing to the property requires a read operation as part of that write. If that’s not the case then we don’t care about thread-safe “read-write” operations at all, and in fact, just want to ensure that when we read the value, we’re guaranteed that value is consistent.

For example, imagine you’ve got a multi-threaded app that stores a string in memory (this isn’t necessarily an accurate representation of the memory access, but rather a demonstration of the problem).

  1. The value starts out as objective-c
  2. Thread 1 attempts to change this to swift
  3. Mid-write, thread 2 attempts to read the value

With an atomic property, thread 2 will either receive objective-c or swift. In a non-atomic world, it could receive garbage swiective-c.

You need to ensure thread safety in the situation that the writing thread needs to read the value before it can write it back. For example, appending to an array or incrementing a counter.

Since making something thread-safe can be “expensive” (at some level it involves locking) then atomic writes can be a good middle ground between complete thread-safety.

It’s worth noting that “thread-safety” depends completely on the context. You can absolutely create something that is fully thread-safe just using atomic properties. In fact, if you make something immutable then you can go with super-fast non-atomic properties. You only need the so-called “barrier-dance” when a “write” operation actually involves both a read and a write.

Hope that makes some sense? I feel like I’ve typed a lot, but not said much :slight_smile:

sam

1 Like

In PART 10: THREAD-SAFE RESOURCES can the isolationQueue be static?

let isolationQueue = dispatch_queue_create(“com.raywenderlich.person.isolation”, DISPATCH_QUEUE_CONCURRENT)

Thanx

jra

Hi @jraldrin

I’m pretty sure there’s no problem with making the isolation queue static, but it could potentially become a bottle-neck for your app.

By making the queue static, you’d be sharing it between all instances of Person. The concurrency model won’t have a problem with this - reads will continue to be asynchronous, and writes synchronous and blocking. The problem comes with that last bit. Since you use a dispatch block to perform writes, and the queue is shared between all instances of Person, writing a value on one instance will prevent access to all other instances. Since this isn’t really the behaviour you want, it’s probably between to not make the dispatch queue static.

Hope that helps

sam