What Does It Mean for Something to Be Thread Safe in iOS

Understanding Swift thread safety

One way to access it asynchronously:

typealias Dict = [Double : [Date:[String:Any]]]
var myDict = Dict()
func getMyDict(f: @escaping (Dict) -> ()) {
queue.async {
DispatchQueue.main.async {
f(myDict)
}
}
}

getMyDict { dict in
assert(Thread.isMainThread)
}

Making the assumption, that queue possibly schedules long lasting closures.

How it works?

You can only access myDict from within queue. In the above function, myDict will be accessed on this queue, and a copy of it gets imported to the main queue. While you are showing the copy of myDict in a UI, you can simultaneously mutate the original myDict. "Copy on write" semantics on Dictionary ensures that copies are cheap.

You can call getMyDict from any thread and it will always call the closure on the main thread (in this implementation).

Caveat:

getMyDict is an async function. Which shouldn't be a caveat at all nowadays, but I just want to emphasise this ;)

Alternatives:

  • Swift Combine. Make myDict a published Value from some Publisher which implements your logic.

  • later, you may also consider to use async & await when it is available.

What does it mean for iOS FileManager to be thread-safe?

Per the same Apple document:
"The methods of the shared FileManager object can be called from multiple threads safely."
If you are using the 'shared FileManager object' (and not an instance), you have thread-safety built-in.

is GCD really Thread-Safe?

You said:

I have studied GCD and Thread-Safe. In apple document, GCD is Thread-Safe that means multiple thread can access. And I learned meaning of Thread-Safe that always give same result whenever multiple thread access to some object.

They are saying the same thing. A block of code is thread-safe only if it is safe to invoke it from different threads at the same time (and this thread safety is achieved by making sure that the critical portion of code cannot run on one thread at the same time as another thread).

But let us be clear: Apple is not saying that if you use GCD, that your code is automatically thread-safe. Yes, the dispatch queue objects, themselves, are thread-safe (i.e. you can safely dispatch to a queue from whatever thread you want), but that doesn’t mean that your own code is necessarily thread-safe. If one’s code is accessing the same memory from multiple threads concurrently, one must provide one’s own synchronization to prevent writes simultaneous with any other access.

In the Threading Programming Guide: Synchronization, which predates GCD, Apple outlines various mechanisms for synchronizing code. You can also use a GCD serial queue for synchronization. If you using a concurrent queue, one achieves thread safety if you use a “barrier” for write operations. See the latter part of this answer for a variety of ways to achieve thread safety.

But be clear, Apple is not introducing a different definition of “thread-safe”. As they say in that aforementioned guide:

When it comes to thread safety, a good design is the best protection you have. Avoiding shared resources and minimizing the interactions between your threads makes it less likely for those threads to interfere with each other. A completely interference-free design is not always possible, however. In cases where your threads must interact, you need to use synchronization tools to ensure that when they interact, they do so safely.

And in the Concurrency Programming Guide: Migrating Away from Threads: Eliminating Lock-Based Code, which was published when GCD was introduced, Apple says:

For threaded code, locks are one of the traditional ways to synchronize access to resources that are shared between threads. ... Instead of using a lock to protect a shared resource, you can instead create a queue to serialize the tasks that access that resource.

But they are not saying that you can just use GCD concurrent queues and automatically achieve thread-safety, but rather that with careful and proper use of GCD queues, one can achieve thread-safety without using locks.


By the way, Apple provides tools to help you diagnose whether your code is thread-safe, namely the Thread Sanitizer (TSAN). See Diagnosing Memory, Thread, and Crash Issues Early.

What is meant by CoreData is not thread safe?

@d11wtq's answer is correct only when writing your own code or designing your own APIs.

It is entirely incorrect when working with a set of APIs and quite specifically wrong when working with Core Data.

In the context of working with Mac OS X and iOS, thread safety must always be considered in the context of working with the system APIs. Even using, say, an NSArray means that you are working with the system APIs.

OR in general what is "not thread
safe" ?

A non-thread safe API is an API where you cannot interact with the API from multiple threads simultaneously. There may also be additional restrictions that most often involve the main thread. For example, almost all drawing operations must occur on the main thread on both Mac OS X and iOS.

The Apple documentation assumes thread safety is the exceptional case. That is, an API is only thread safe if the documentation explicitly claims thread safety. If there is no mention of thread safety, you must assume that the API is not thread safe.

In Obj-C, what does it mean in simple
terms; "CoreData is not thread safe"

That statement is not quite correct, but it is a safe assumption.

In Core Data's case, the thread interaction behavior is extremely well documented.

In short, parts of the API are thread safe (the store coordinator, for example) and parts are quite explicitly not thread safe. While the MOC provides lock and unlock methods, you can also use external locking. But don't. It will be less efficient and more fragile; significantly so. In general, don't use the internal locking either. CoreData is optimized around having a context per thread/queue.

(Answer fixed based on TC's feedback. Thanks.)

Is the objc_msgSend function thread-safe?

The details of this answer depend on the specifics of what you mean by "thread-safe", but for most definitions of "thread-safe" (e.g., calling this code from multiple threads will leave your program in a consistent state without unintended interactions), then the answer is yes, objc_msgSend is thread-safe.

The specifics of how this is done are complex, but some context:

  1. A function is inherently thread-safe if it does not modify state shared across threads in a way that could lead to unintended consequences
    • For example,

      func add(_ a: Int, _ b: Int) -> Int {
      a + b
      }

      is inherently thread-safe — you can't call it in a way that would cause it to mutate shared state somehow

  2. A function is thread-safe if it does modify state shared across threads, but in a way that is coordinated with other threads reading that state

objc_msgSend falls into the latter camp: there are cases in which it does need to modify shared state (entries in the method cache), but it does so in a coordinated way.


Some helpful references, written by Mike Ash (who last I knew, was still working on the Obj-C runtime at Apple):

  • Friday Q&A 2017-06-30: Dissecting objc_msgSend on ARM64
  • Friday Q&A 2015-05-29: Concurrent Memory Deallocation in the Objective-C Runtime
  • objc_msgSend's New Prototype

In "Dissecting objc_msgSend on ARM64", Mike goes through the specifics of how objc_msgSend works, line-by-line. While the code has changed a bit since 2017, the specifics are all largely the same:

  1. It gets the class of the target object passed in
  2. It finds the method cache for that class
  3. It uses the selector called on the target object to look up a method implementation for that method in the cache
    • If a method is not found in the cache, it falls back to looking it up, and possibly inserts it into the cache
  4. It calls the implementation of that method directly

Of these operations, (1), (2), and (4) are most obviously thread-safe: (1) and (4) are inherently thread-safe (and don't rely on other threads at all), and (2) performs an atomic read that is safe across threads.

(3) is the trickier operation, since the method cache is shared for all instances of a class, and if you're calling a method on multiple instances of the same class at the same time, they might be digging through the method cache simultaneously. "Concurrent Memory Deallocation in the Objective-C Runtime" goes into this a little bit more on how this is done, but the general gist is that there are plenty of cool synchronization tricks that you can without needing to lock across threads. The full explanation is out of scope (and the article goes into a lot of detail anyway), but in a comment on "Dissecting objc_msgSend on ARM64" from Blaine Garst (one of the original authors of many of the features you might recognize as part of the Objective-C runtime):

Mike, you missed explaining the greatest secret of the messenger, one that Matt and Dave and I cooked up on the black couches at Seaport: how it is that multiple clients can read a mutable hash table without synchronization. It stumps people to this day. The answer is that the messenger uses a specialized garbage collector! I had put locks in when we first tried to do multi-threaded ObjC and it was miserable and then I read a paper by David Black where the kernel would reset the PC back to the start of a critical range for a mutex, and, well, we use the absense of a PC in critical ranges to deduce safety of reclaiming stale caches.

So, this operation is coordinated across threads too.


All of the work that objc_msgSend does is safe across thread boundaries, so much so that you can call methods on the same exact object across any number of threads at the exact same time, and it will work just fine. (Whether the implementation of the method is thread-safe is something else entirely.)

And in the end, it has to be. objc_msgSend is the backbone of pretty much all of Objective-C. If it wasn't thread-safe, it would be impossible to write multi-threaded Objective-C code. But luckily it is, and you can rely on it working at scale.

making a class thread safe in iOS

The easiest and best way to make a class thread safe is to make it immutable. Then you don't have to deal with any of this. It just works. It really is worth your time to think about whether you need mutability on multiple threads.

But if an immutable class creates significant problems for your design, then generally the best way to implement it is with GCD rather than locks. GCD has much lower overhead and is generally speaking easier to get right.

In this particular case, I'd implement it along these lines (untested, and I've been working in Swift for a while now, so forgive me if I drop a semicolon):

#import "SafeQueue.h"

@interface SafeQueue()

@property (strong, nonatomic) NSMutableArray *data;
@property (strong, nonatomic) dispatch_queue_t dataQueue;

@end

@implementation SafeQueue

- (instancetype)init {
if (self = [super init]) {
_dataQueue = dispatch_queue_create("SafeQueue.data", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}

- (id)peek {
__block id result = nil;
dispatch_sync(self.dataQueue, ^{ result = [self.data firstObject] });
return result;
}

- (NSUInteger)length {
__block NSUInteger result = 0;
dispatch_sync(self.dataQueue, ^{ result = [self.data count] });
return result;
}

- (void)enqueue:(id)datum {
dispatch_barrier_async(self.dataQueue, ^{ [self.data addObject:datum] });
}

// other methods omitted...

@end

Note the use of dispatch_sync for all readers and dispatch_barrier_async for all writers. This is how you keep your overhead to a minimum by allowing parallel readers and exclusive writers. And if there is no contention (which is the normal case), dispatch_sync has much lower overhead than a lock (NSLock or @synchronized or even a pthreads lock).

See Migrating Away from Threads for more advice from Apple on how to better deal with concurrency in Cocoa.



Related Topics



Leave a reply



Submit