objc_sync_enter / objc_sync_exit not working with DISPATCH_QUEUE_PRIORITY_LOW objc_sync_enter / objc_sync_exit not working with DISPATCH_QUEUE_PRIORITY_LOW multithreading multithreading

objc_sync_enter / objc_sync_exit not working with DISPATCH_QUEUE_PRIORITY_LOW


objc_sync_enter is an extremely low-level primitive, and isn't intended to be used directly. It's an implementation detail of the old @synchronized system in ObjC. Even that is extremely out-dated and should generally be avoided.

Synchronized access in Cocoa is best achieved with GCD queues. For example, this is a common approach that achieves a reader/writer lock (concurrent reading, exclusive writing).

public class UserData {    private let myPropertyQueue = dispatch_queue_create("com.example.mygreatapp.property", DISPATCH_QUEUE_CONCURRENT)    private var _myProperty = "" // Backing storage    public var myProperty: String {        get {            var result = ""            dispatch_sync(myPropertyQueue) {                result = self._myProperty            }            return result        }        set {            dispatch_barrier_async(myPropertyQueue) {                self._myProperty = newValue            }        }    }}

All your concurrent properties can share a single queue, or you can give each property its own queue. It depends on how much contention you expect (a writer will lock the entire queue).

The "barrier" in "dispatch_barrier_async" means that it is the only thing allowed to run on the queue at that time, so all previous reads will have completed, and all future reads will be prevented until it completes. This scheme means that you can have as many concurrent readers as you want without starving writers (since writers will always be serviced), and writes are never blocking. On reads are blocking, and only if there is actual contention. In the normal, uncontested case, this is extremely very fast.


Are you 100% sure your blocks are actually executing on different threads?

objc_sync_enter() / objc_sync_exit() are guarding you only from object being accessed from different threads. They use a recursive mutex under the hood, so they won't either deadlock or prevent you from repeatedly accessing object from the same thread.

So if you lock in one async block and unlock in another one, the third block executed in-between can have access to the guarded object.


This is one of those very subtle nuances that is easy to miss.

Locks in Swift

You have to really careful what you use as a Lock. In Swift, String is a struct, meaning it's pass-by-value.

Whenever you call objc_sync_enter(g), you are not giving it g, but a copy of g. So each thread is essentially creating its own lock, which in effect, is like having no locking at all.

Use NSObject

Instead of using a String or Int, use a plain NSObject.

let lock = NSObject()func waitAndStartWriting() {    log("wait Writing")    objc_sync_enter(lock)    log("enter writing")}func finishWriting() {    objc_sync_exit(lock)    log("exit writing")}

That should take care of it!