There may be perceived performance advantages that accrue from using multiple threads with Core Data. In particular, if you execute a large or complex fetch that takes some time, you might execute the full fetch on a background thread. It is important to consider, however, that most of the Application Kit is not thread safe and that you need to take considerable care that object graphs do not get into an inconsistent state.
Thread Safety Fundamentals
General Guidelines
Locking
Fetching in a Background Thread
Saving
There are several issues to bear in mind when using multi-threading in a Core Data application:
Any time you manipulate or access your object graph, you may be using the associated managed object context.
Core Data does not present a situation where reads are "safe" but changes are "dangerous"—every operation is "dangerous" because every operation can trigger faulting.
Managed objects themselves are not thread safe.
If you want to work with a managed object across different threads, you must lock its context (see NSLocking
). If you try to pass actual objects, share contexts between threads, and so on, you must be extremely careful about locking (and as a consequence you are likely to negate any benefit you may otherwise derive from multi-threading). Working with a managed object across different threads is therefore strongly discouraged, as described in “General Guidelines.”
Passing object IDs (which are immutable) across thread boundaries makes dealing with threading much easier.
To make a managed object from one context visible in another, you pass its managed object ID and use objectWithID:
on the receiving thread's managed object context to get a local version of the managed object. Note that the corresponding managed objects must have been saved—you cannot pass the ID of a newly-inserted managed object to another context.
On Mac OS X v10.5, you can use the API provided by NSFetchRequest
to facilitate working with data across threads. For example, you can configure a fetch request to return objectIDs but include the row data (and update the row cache)—this can be useful if you're just going to pass those object IDs from a background thread to another thread.
A persistent store coordinator provides to its managed object contexts the façade of one virtual store.
For completely concurrent operations you need a different coordinator for each thread.
On Mac OS X v10.5, executeFetchRequest:error:
intrinsically scales its behavior appropriately for the hardware and work load.
If necessary, the Core Data will create additional private threads to optimize fetching performance. You will not improve absolute fetching speed by creating background threads for the purpose (although it may still be appropriate to fetch in a background thread for enhanced responsiveness—that is, to prevent your application from blocking).
It is important to consider the application environment.
For the most part, the Application Kit is not thread safe; in particular, Cocoa bindings and controllers are not thread safe—if you are using these technologies, multi-threading may be complex.
In general, you should not use any given managed object or managed object context in more than one thread. Instead, you should give each thread its own entirely private managed object context and keep their associated object graphs separated on a per-thread basis. If you do this, there is no need to lock contexts during access. You can use one persistent store coordinator per group of cooperating threads (for example, for your application or for each document).
There are three patterns you can adopt to support multi-threading in a Core Data application; in order of preference they are:
Create a separate managed object context for each thread and share a single persistent store coordinator.
If you need to “pass” managed objects between threads, you just pass their object IDs.
If you want to aggregate a number of operations in one context together as if a virtual single transaction, you can lock the persistent store coordinator to prevent other managed object contexts using the persistent store coordinator over the scope of several operations.
Create a separate managed object context and persistent store coordinator for each thread.
If you need to “pass” managed objects between threads, you just pass their object IDs.
Using a separate persistent store coordinator for each thread allows for completely concurrent operations.
Pass managed objects or managed object contexts between threads.
This approach is strongly discouraged. You must ensure that you apply locks as appropriate and necessary.
Generally, you only need to lock a managed object context (and not even then if you ensure that each thread has its own private context, as described in “General Guidelines”). If you do choose to share a managed object context or a persistent store coordinator between threads, you must ensure that any method invocations are made from a thread-safe scope. For locking, you should use the NSLocking
methods on managed object context and persistent store coordinator instead of implementing your own mutexes. These methods help provide contextual information to the framework about the application's intent—that is, in addition to providing a mutex, they help scope clusters of operations.
Typically you lock the context or coordinator using tryLock
or lock
. If you do this, the framework will ensure that what it does behind the scenes is also thread-safe. For example, if you create one context per thread, but all pointing to the same persistent store coordinator, Core Data takes care of accessing the coordinator in a thread-safe way (NSManagedObjectContext
's lock
and unlock
methods handle recursivity).
If you lock (or successfully tryLock
) a context, that context must be retained until you invoke unlock
. If you don’t properly retain a context in a multi-threaded environment, you may cause a deadlock.
One of the simplest multi-threading techniques you can use with Core Data to improve application responsiveness is to execute a fetch request on a background thread. (Note that this technique is only useful if you are using an SQLite store, since data from binary and XML stores is read into memory immediately on open.) This means that if a fetch is complicated or returns a large amount of data, you can return control to the user and display results as they arrive. For an example of how to do this, see the BackgroundFetching
example in /Developer/Examples/CoreData/
.
You use two managed object contexts associated with a single persistent store coordinator. You fetch in one managed object context on a background thread, and pass the object IDs of the fetched objects to another thread. In the second thread (typically the application's main thread, so that you can then display the results), you use the second context to fault in objects with those object IDs (you use objectWithID:
to instantiate the object).
Performing a save operation in a detached thread is error-prone unless you take additional steps to prevent the application from quitting before the save is completed. Specifically, all NSThread
-based threads are "detached" (see the documentation for pthread
for complete details) and a process runs only until all not-detached threads have exited. The work a detached thread is performing is therefore considered optional, and the process may terminate at any time. (Most users do not consider saving to be optional work!) In Cocoa, only the main thread is not-detached. If you need to save on other threads, you must write additional code such that the main thread prevents the application from quitting until all the save operation is complete.
© 2004, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-04)