I have a question about thread safety of the following code example from Apple (from GameKit programming guide)
This is to load achievements from game center and save it locally:
Step 1) Add a mutable dictionary property to your class that report achievements. This dictionary stores the collection of achievement objects.
#property(nonatomic, retain) NSMutableDictionary *achievementsDictionary;
Step 2) Initialize the achievements dictionary.
achievementsDictionary = [[NSMutableDictionary alloc] init];
Step 3) Modify your code that loads loads achievement data to add the achievement objects to the dictionary.
{
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
{
if (error == nil)
{
for (GKAchievement* achievement in achievements)
[achievementsDictionary setObject: achievement forKey: achievement.identifier];
}
}];
My question is as follows - achievementsDictionary object is being modified in the completion handler, without any locks of sort. Is this allowed because completion handlers are a block of work that will be guaranteed by iOS to be executed as unit on the main thread? And never run into thread safety issues?
In another Apple sample code (GKTapper), this part is handled differently:
#property (retain) NSMutableDictionary* earnedAchievementCache; // note this is atomic
Then in the handler:
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error)
{
if(error == NULL)
{
NSMutableDictionary* tempCache= [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement* score in scores)
{
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache= tempCache;
}
}];
So why the different style, and is one way more correct than the other?
Is this allowed because completion handlers are a block of work that will be guaranteed by iOS to be executed as unit on the main thread? And never run into thread safety issues?
This is definitely not the case here. The documentation for -loadAchievementsWithCompletionHandler: explicitly warns that the completion handler might be called on a thread other than the one you started the load from.
Apple's "Threading Programming Guide" classifies NSMutableDictionary among thread-unsafe classes, but qualifies this with, "In most cases, you can use these classes from any thread as long as you use them from only one thread at a time."
So, if both apps are designed such that nothing will be working with the achievement cache till the worker task has finished updating it, then no synchronization would be necessary. This is the only way in which I can see the first example as being safe, and it's a tenuous safety.
The latter example looks like it's relying on the atomic property support to make the switcheroo between the old cache and the new cache. This should be safe, provided all access to the property is via its accessors rather than direct ivar access. This is because the accessors are synchronized with respect to each other, so you do not risk seeing a half-set value. Also, the getter retains and autoreleases the returned value, so that code with the old version will be able to finish working with it without crashing because it was released in the middle of its work. A nonatomic getter simply returns the object directly, which means that it could be deallocated out from under your code if a new value were set for that property by another thread. Direct ivar access can run into the same problem.
I would say the latter example is both correct and elegant, though perhaps a bit over-subtle without a comment explaining how crucial the atomicity of the property is.
Related
Updated: I have prepared the sample which is reproduce the issue without magical record.Please download the test project using following URL:
https://www.dsr-company.com/fm.php?Download=1&FileToDL=DeadLockTest_CoreDataWithoutMR.zip
The provided project has following problem: deadlock on fetch
in performBlockAndWait called from main thread.
The issue is reproduced if code is compiled using XCode version > 6.4.
The issue is not reproduced if code is compiled using xCode == 6.4.
Old question was:
I am working on the support of IOS mobile application.
After the recent update of Xcode IDE from version 6.4 to version 7.0 ( with IOS 9 support ) I have faced with critical issue - application hangup.
The same build of the application ( produced from the same sources ) with xCode 6.4 works OK.
So, if the application is built using xCode > 6.4 - application hangs up on some cases.
if the application is built using xCode 6.4 - application works OK.
I have spent some time to research the issue and as the result I have prepared the test application with similar case like in my application which reproduces the problem.
The test application hangup on the Xcode >= 7.0 but works correctly on the Xcode 6.4
Download link of test sources:
https://www.sendspace.com/file/r07cln
The requirements for the test application is:
1. cocoa pods manager must be installed in the system
2. MagicalRecord framework of version 2.2.
Test application works in the following way:
1. At the start of the application it creates test database with 10000 records of simple entities and saves them to persistent store.
2. At the first screen of the application in the method viewWillAppear: it runs the test which causes deadlock.
Following algorithm is used:
-(NSArray *) entityWithId: (int) entityId inContext:(NSManagedObjectContext *)localContext
{
NSArray * results = [TestEntity MR_findByAttribute:#"id" withValue:[ NSNumber numberWithInt: entityId ] inContext:localContext];
return results;
}
…..
int entityId = 88;
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = #"childContext1";
NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = #"childContext2";
NSArray *results = [self entityWithId:entityId inContext: childContext2];
for(TestEntity *d in results)
{
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name); /// this line is the reason of the hangup
}
dispatch_async(dispatch_get_main_queue(), ^
{
int entityId2 = 11;
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"id=%d", entityId2];
NSArray *a = [ TestEntity MR_findAllWithPredicate: predicate2 inContext: childContext2];
for(TestEntity *d in a)
{
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name);
}
});
Two managed object contexts are created with concurrency type == NSPrivateQueueConcurrencyType (please check the code of MR_context of magical record framework). Both contexts has parent context with
concurrency type = NSMainQueueConcurrencyType. From the main thread application performs fetch in sync manner ( MR_findByAttribute and MR_findAllWithPredicate
are used performBlockAndWait with fetch request inside ). After the first fetch the second fetch is schedule on the main thread using dispatch_async().
As a result the application hangs up. It seems that deadlock has happened, please check the screenshot of the stack:
 here is the link, my reputation is too low to post images. https://cdn.img42.com/34a8869bd8a5587222f9903e50b762f9.png)
If to comment the line
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name); /// this line is the reason of the hangup
(which is the line 39 in ViewController.m of the test project ) the application becomes working OK. I believe this is because there is no read of name field of the test entity.
So with the commented line
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name);
there is no hangup on binaries built both with Xcode 6.4 and Xcode 7.0.
With the uncommented line
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name);
there is hangup on binary built with Xcode 7.0 and there is no hangup on binary built with Xcode 6.4.
I believe the issue is happens because of lazy-loading of entity data.
Has anybody problem with the described case? I will be grateful for any help.
This is why I don't use frameworks that abstract (i.e., hide) too many details of core data. It has very complex use patterns, and sometimes you need to know the details of how they interoperate.
First, I know nothing about magical record except that lots of people use it so it must be pretty good at what it does.
However, I immediately saw several completely wrong uses of core data concurrency in your examples, so I went and looked at the header files to see why your code made the assumptions that it does.
I don't mean to bash you at all, though this may seem like it at first blush. I want to help educate you (and I used this as an opportunity to take a peek at MR).
From a very quick look at MR, I'd say you have some misunderstandings of what MR does, and also core data's general concurrency rules.
First, you say this...
Two managed object contexts are created with concurrency type ==
NSPrivateQueueConcurrencyType (please check the code of MR_context of
magical record framework). Both contexts has parent context with
concurrency type = NSMainQueueConcurrencyType.
which does not appear to be true. The two new contexts are, indeed, private-queue contexts, but their parent (according to the code I glanced at on github) is the magical MR_rootSavingContext, which itself is also a private-queue context.
Let's break down your code example.
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = #"childContext1";
NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = #"childContext2";
So, you now have two private-queue MOCs (childContext1 and childContext2), both children of another anonymous private-queue MOC (we will call savingContext).
NSArray *results = [self entityWithId:entityId inContext: childContext2];
You then perform a fetch on childContext1. That code is actually...
-(NSArray *) entityWithId:(int)entityId
inContext:(NSManagedObjectContext *)localContext
{
NSArray * results = [TestEntity MR_findByAttribute:#"id"
withValue:[NSNumber numberWithInt:entityId]
inContext:localContext];
return results;
}
Now, we know that the localContext in this method is, in this case, another pointer to childContext2 which is a private-queue MOC. It is 100% against the concurrency rules to access a private-queue MOC outside of a call to performBlock. However, since you are using another API, and the method name offers no assistance to know how the MOC is being accessed, we need to go look at that API and see if it hides the performBlock to see if you are accessing it correctly.
Unfortunately, the documentation in the header file offers no indication, so we have to look at the implementation. That call ends up calling MR_executeFetchRequest... which does not indicate in the documentation how it handles the concurrency either. So, we go look at its implementation.
Now, we are getting somewhere. This function does try to safely access the MOC, but it uses performBlockAndWait which will block when it is called.
This is an extremely important piece of information, because calling this from the wrong place can indeed cause a deadlock. Thus, you must be keenly aware that performBlockAndWait is being called anytime you execute a fetch request. My own personal rule is to never use performBlockAndWait unless there is absolutely no other option.
However, this call here should be completely safe... assuming it is not being called from within the context of the parent MOC.
So, let's look at the next piece of code.
for(TestEntity *d in results)
{
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name); /// this line is the reason of the hangup
}
Now, this is not the fault of MagicalRecord, because MR isn't even being used directly here. However, you have been trained to use those MR_ methods, which require no knowledge of the concurrency model, so you either forget or never learn the concurrency rules.
The objects in the results array are all managed objects that live in the childContext2 private-queue context. Thus, you may not ever access them without paying homage to the concurrency rules. This is a clear violation of the concurrency rules. While developing your application, you should enable concurrency debugging with the argument -com.apple.CoreData.ConcurrencyDebug 1.
This code snippet must be wrapped in either performBlock or performBlockAndWait. I hardly ever use performBlockAndWait for anything because it has so many drawbacks - deadlocks being one of them. In fact, just seeing the use of performBlockAndWait is a very strong indication that your deadlock is happening in there and not on the line of code that you indicate. However, in this case, it is at least as safe as the previous fetch, so let's make it a bit safer...
[childContext2 performBlockAndWait:^{
for (TestEntity *d in results) {
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name);
}
}];
Next, you dispatch to the main thread. Is that because you just want something to occur on a subsequent event loop cycle, or is it because this code is already running on some other thread? Who knows. However, you have the same problem here (I reformatted your code for readability as a post).
dispatch_async(dispatch_get_main_queue(), ^{
int entityId2 = 11;
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"id=%d", entityId2];
NSArray *a = [TestEntity MR_findAllWithPredicate:predicate2
inContext:childContext2];
for (TestEntity *d in a) {
NSLog(#"e from fetchRequest %# with name = '%#'", d, d.name);
}
});
Now, we know that code starts out running on the main thread, and the search will call performBlockAndWait but your subsequent access in the for-loop again violates the core data concurrency rules.
Based on that, the only real problems I see are...
MR seems to honor the core data concurrency rules within their API, but you must still follow the core data concurrency rules when accessing your managed objects.
I really don't like the use of performBlockAndWait as it's just a problem waiting to happen.
Now, let's take a look at the screenshot of your hang. Hmmm... it's a classic deadlock, but it makes no sense because the deadlock happens between the main thread and the MOC thread. That can only happen if the main-queue MOC is a parent of this private-queue MOC, but the code shows that is not the case.
Hmmm... it didn't make sense, so I downloaded your project, and looked at the source code in the pod you uploaded. Now, that version of the code uses the MR_defaultContext as the parent of all MOCs created with MR_context. So, the default MOC is, indeed, a main-queue MOC, and now it all makes perfect sense.
You have a MOC as a child of a main-queue MOC. When you dispatch that block to the main queue, it's is now running as a block on the main queue. The code then calls performBlockAndWait on a context that is a child of a MOC for that queue, which is a huge no-no, and your are almost guaranteed to get a deadlock.
So, it seems that MR has since changed their code from using a main-queue as the parent of new contexts to using a private-queue as the parent of new contexts (most likely due to this exact problem). So, if you upgrade to the latest version of MR you should be fine.
However, I would still warn you that if you want to use MR in multithreading ways, you must know exactly how they handle the concurrency rules, and you must also make sure you obey them anytime you are accessing any core-data objects that are not going through the MR API.
Finally, I'll just say that I've done tons and tons of core data stuff, and I've never used an API that tries to hide the concurrency issues from me. The reason is that there are too many little corner cases, and I'd rather just deal with them in a pragmatic way up front.
Finally, you should almost never use performBlockAndWait unless you know exactly why its the only option. Having it be used as part of an API underneath you is even more scary... to me at least.
I hope this little jaunt has enlightened and helped you (and possible some others). It certainly shed a little bit of light for me, and helped reestablish some of my previous unfounded skittishness.
Edit
This is in response to the "non-magical-record" example you provided.
The problem with this code is the exact same problem I described above, relative to what was happening with MR.
You have a private-queue context, as a child to a main-queue context.
You are running code on the main queue, and you call performBlockAndWait on the child context, which has to then lock its parent context as it tries to execute the fetch.
It is called a deadlock, but the more descriptive (and seductive) term is deadly embrace.
The original code is running on the main thread. It calls into a child context to do something, and it does nothing else until that child complete.
That child then, in order to complete, needs the main thread to do something. However, the main thread can't do anything until the child is done... but the child is waiting for the main thread to do something...
Neither one can make any headway.
The problem you are facing is very well documented, and in fact, has been mentioned a number of times in WWDC presentations and multiple pieces of documentation.
You should NEVER call performBlockAndWait on a child context.
The fact that you got away with it in the past is just a "happenstance" because it's not supposed to work that way at all.
In reality, you should hardly every call performBlockAndWait.
You should really get used to doing asynchronous programming. Here is how I would recommend you rewrite this test, and whatever it is like that prompted this issue.
First, rewrite the fetch so it works asynchronously...
- (void)executeFetchRequest:(NSFetchRequest *)request
inContext:(NSManagedObjectContext *)context
completion:(void(^)(NSArray *results, NSError *error))completion
{
[context performBlock:^{
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (completion) {
completion(results, error);
}
}];
}
Then, you change you code that calls the fetch to do something like this...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: testEntityDescription ];
[request setPredicate: predicate2 ];
[self executeFetchRequest:request
inContext:childContext2
completion:^(NSArray *results, NSError *error) {
if (results) {
for (TestEntity *d in results) {
NSLog(#"++++++++++ e from fetchRequest %# with name = '%#'", d, d.name);
}
} else {
NSLog(#"Handle this error: %#", error);
}
}];
We switched over to XCode7 and I just ran into a similar deadlock issue with performBlockAndWait in code that works fine in XCode6.
The issue seems to be an upstream use of dispatch_async(mainQueue, ^{ ... to pass back the result from a network operation. That call was no longer needed after we added concurrency support for CoreData, but somehow it was left and never seemed to cause a problem until now.
It's possible that Apple changed something behind the scenes to make potential deadlocks more explicit.
The following code creates a memory leak. An asynchronous background process downloads images in tmp_pack_folder and another background thread is checking if the image count matches the total count expected, and then makes the images available to users once the download is complete.
The issue is that if the background process that is downloading images to the tmp_pack_folder fails for some reason, the following code becomes an infinite loop. This is a rare case, but when it does there is a memory leak. getAllFileNamesinFolder method is actually calling contentsOfDirectoryAtPath:bundleRoot of NSFileManager and it is called repeatedly. How to do I properly deallocate memory in this case (apart from preventing the infinite loop to begin with)
NSString *tmp_pack_folder = [packid stringByAppendingString:#"_tmp"];
if([fileMgr folderExists: tmp_pack_folder]){
NSArray *packImages = [fileMgr getAllFileNamesInFolder:tmp_pack_folder];
while(packImages.count != arrImages.count ){
packImages = [fileMgr getAllFileNamesInFolder:tmp_pack_folder]; //get the contents of the folder again.
if(cancel==YES){
break;
}
}
}
You say that you will rework this to "prevent the infinite loop." You should take that a step further and eliminate the loop altogether. If you ever find yourself with code that loops, polling some status, there's invariably an alternate, more efficient design. Bottom line, your memory situation is not the real problem: It's merely a symptom of a broader design issue.
I'd advise you move to an event-driven approach. So, rather than having a method that repeatedly performs the "am I done yet" logic, you should only check this status when triggered by the appropriate event (i.e. only when a download finishes/fails, and not before). This loop is probably causing to your memory problem, so don't fix the memory problem, but rather eliminate the loop altogether.
In answer to your question, one possible source of the memory problem arises from autorelease objects. These are objects that are allocated in such a manner that they are not released immediately when you're done with them, but rather only when the autorelease pool is drained (which generally happens for you automatically when you yield back to the app's run loop). But if you have some large loop that you repeatedly call, you end up adding lots of objects to an autorelease pool that isn't drained in a timely manner.
In some special cases, if you truly needed some loop (and to be clear, that's not the case here; you neither need nor want a loop in this case), you could employ your own custom #autoreleasepool, through which you'd effectively control the frequency of the draining of the pool.
But, at the risk of belaboring the point, this is simply not one of those situations. Don't use your own autorelease pool. Get rid of the loop. Only trigger the "am I done yet" logic when a download finishes/fails, and your problem should go away.
It's too bad Objective-C doesn't give us javascript-like promises. The way I solve this problem is by giving my asynch task a caller's interface like this:
- (void)doAsynchThingWithParams:(id)params completion:(void (^)(id))completion;
The params parameterize whatever the task is, and the completion handler takes result of the task.
This let's me treat several concurrent tasks like a todo list, with a completion handler that gets called with all the results once they've arrived.
// array is an array of params for each task e.g. urls for making url requests
// completion is called when all are complete with an array of results
- (void)doManyThingsWithParams:(NSArray *)array completion:(void (^)(NSArray *))completion {
NSMutableArray *todoList = [array mutableCopy];
NSMutableArray *results = [NSMutableArray array];
// results will always have N elements, one for each task
// nulls can be replaced by either good results or NSErrors
for (int i=0; i<array.count; ++i) results[i] = [NSNull null];
for (id params in array) {
[self doAsynchThingWithParams:params completion:^(id result) {
if (result) {
NSInteger index = [array indexOfObject:params];
[results replaceObjectAtIndex:index withObject:result];
}
[todoList removeObject:params];
if (!todoList.count) completion(results);
}];
}
}
I am trying very hard to understand everything about MagicalRecord and CoreData. So let's say, I have 2 pieces of code doing same thing, where tallyM is a managed object running in MR_defaultContext.
Option 1:
Tally *tallyM = (Tally *)[Tally MR_findFirstWithPredicate:predicateM];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Tally *tallyMLocal = [tallyM MR_inContext:localContext];
tallyMLocal.tl_countMale = [NSString stringWithFormat:#"%ld", (long)uiTallyMaleCounter];
} completion:^(BOOL success, NSError *error) {
[self updateTallies_APICall:[tallyM MR_inContext:[NSManagedObjectContext MR_defaultContext]]];
}];
Option 2:
Tally *tallyM = (Tally *)[Tally MR_findFirstWithPredicate:predicateM];
tallyM.tl_countMale = [NSString stringWithFormat:#"%ld", (long)uiTallyMaleCounter];
[tallyM.managedObjectContext MR_saveToPersistentStoreAndWait];
[self updateTallies_APICall:[tallyM MR_inContext:[NSManagedObjectContext MR_defaultContext]]];
Questions:
Which one is better? I understand that saveWithBlock can be used when you need async save, but is there any other difference? Is Option 1 safer or better in any way than Option 2?
In Option 1, I have tallyM which runs in MR_defaultContext. Then inside saveWithBlock, I change tallyM, by changing tallyMLocal, and saving contexts. Can I be 100% sure that after saveWithBlock runs, in completion handler (when I need to continue working with tallyM), tallyM (which is still running in MR_defaultContext) will have the tl_countMale updated?
In Option 1, in completion handler, do I still need to call below code? I assume (already checked with console, but just want to be sure) that tallyM still runs in MR_defaultContext, after saveWithBlock is executed. So is it needed to call again MR_inContext?
[tallyM MR_inContext:[NSManagedObjectContext MR_defaultContext]]
Let's say I don't need async save at all. So I can use Option 2, or saveWithBlockAndWait. Is saveWithBlockAndWait in any way better than Option 2?
I just want to make sure I finally correctly understood how MagicalRecords and CoreData behaves.
Personally, I would avoid the pattern in Option 2 there. The idea being that you should use a single Managed Object Context as the scope for operations on a collection of Managed Objects. That is why most examples use the following pattern:
NSManagedObjectContext *localContext = //...;
NSManagedObject *localObject = [otherObject MR_inContext:localContext];
///make changes to localObject
[localContext MR_saveToPersistentStoreAndWait];
The [MagicalRecord saveWithBlock:] method basically implements this pattern in a more convenient API.
I would also recommend not using the defaultContext implicitly. Be more explicit about that in your code because you may need to swap that out when your app starts to deal with threads.
The completion handlers are written in a way that they are always called back after the save operation is 100% complete. I recommend reading the source code to see for yourself.
With reference to this answer, I am wondering is this correct?
#synchronized does not make any code "thread-safe"
As I tried to find any documentation or link to support this statement, for no success.
Any comments and/or answers will be appreciated on this.
For better thread safety we can go for other tools, this is known to me.
#synchronized does make code thread safe if it is used properly.
For example:
Lets say I have a class that accesses a non thread safe database. I don't want to read and write to the database at the same time as this will likely result in a crash.
So lets say I have two methods. storeData: and readData on a singleton class called LocalStore.
- (void)storeData:(NSData *)data
{
[self writeDataToDisk:data];
}
- (NSData *)readData
{
return [self readDataFromDisk];
}
Now If I were to dispatch each of these methods onto their own thread like so:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[LocalStore sharedStore] storeData:data];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[LocalStore sharedStore] readData];
});
Chances are we would get a crash. However if we change our storeData and readData methods to use #synchronized
- (void)storeData:(NSData *)data
{
#synchronized(self) {
[self writeDataToDisk:data];
}
}
- (NSData *)readData
{
#synchronized(self) {
return [self readDataFromDisk];
}
}
Now this code would be thread safe. It is important to note that if I remove one of the #synchronized statements however the code would no longer be thread safe. Or if I were to synchronize different objects instead of self.
#synchronized creates a mutex lock on the object you are syncrhonizing. So in other words if any code wants to access code in a #synchronized(self) { } block it will have to get in line behind all previous code running within in that same block.
If we were to create different localStore objects, the #synchronized(self) would only lock down each object individually. Does that make sense?
Think of it like this. You have a whole bunch of people waiting in separate lines, each line is numbered 1-10. You can choose what line you want each person to wait in (by synchronizing on a per line basis), or if you don't use #synchronized you can jump straight to the front and skip all the lines. A person in line 1 doesn't have to wait for a person in line 2 to finish, but the person in line 1 does have to wait for everyone in front of them in their line to finish.
I think the essence of the question is:
is the proper use of synchronize able to solve any thread-safe
problem?
Technically yes, but in practice it's advisable to learn and use other tools.
I'll answer without assuming previous knowledge.
Correct code is code that conforms to its specification. A good specification defines
invariants constraining the state,
preconditions and postconditions describing the effects of the operations.
Thread-safe code is code that remains correct when executed by multiple threads. Thus,
No sequence of operations can violate the specification.1
Invariants and conditions will hold during multithread execution without requiring additional synchronization by the client2.
The high level takeaway point is: thread-safe requires that the specification holds true during multithread execution. To actually code this, we have to do just one thing: regulate the access to mutable shared state3. And there are three ways to do it:
Prevent the access.
Make the state immutable.
Synchronize the access.
The first two are simple. The third one requires preventing the following thread-safety problems:
liveness
deadlock: two threads block permanently waiting for each other to release a needed resource.
livelock: a thread is busy working but it's unable to make any progress.
starvation: a thread is perpetually denied access to resources it needs in order to make progress.
safe publication: both the reference and the state of the published object must be made visible to other threads at the same time.
race conditions A race condition is a defect where the output is dependent on the timing of uncontrollable events. In other words, a race condition happens when getting the right answer relies on lucky timing. Any compound operation can suffer a race condition, example: “check-then-act”, “put-if-absent”. An example problem would be if (counter) counter--;, and one of several solutions would be #synchronize(self){ if (counter) counter--;}.
To solve these problems we use tools like #synchronize, volatile, memory barriers, atomic operations, specific locks, queues, and synchronizers (semaphores, barriers).
And going back to the question:
is the proper use of #synchronize able to solve any thread-safe
problem?
Technically yes, because any tool mentioned above can be emulated with #synchronize. But it would result in poor performance and increase the chance of liveness related problems. Instead, you need to use the appropriate tool for each situation. Example:
counter++; // wrong, compound operation (fetch,++,set)
#synchronize(self){ counter++; } // correct but slow, thread contention
OSAtomicIncrement32(&count); // correct and fast, lockless atomic hw op
In the case of the linked question you could indeed use #synchronize, or a GCD read-write lock, or create a collection with lock stripping, or whatever the situation calls for. The right answer depend on the usage pattern. Any way you do it, you should document in your class what thread-safe guarantees are you offering.
1 That is, see the object on an invalid state or violate the pre/post conditions.
2 For example, if thread A iterates a collection X, and thread B removes an element, execution crashes. This is non thread-safe because the client will have to synchronize on the intrinsic lock of X (synchronize(X)) to have exclusive access. However, if the iterator returns a copy of the collection, the collection becomes thread-safe.
3 Immutable shared state, or mutable non shared objects are always thread-safe.
Generally, #synchronized guarantees thread safety, but only when used correctly. It is also safe to acquire the lock recursively, albeit with limitations I detail in my answer here.
There are several common ways to use #synchronized wrong. These are the most common:
Using #synchronized to ensure atomic object creation.
- (NSObject *)foo {
#synchronized(_foo) {
if (!_foo) {
_foo = [[NSObject alloc] init];
}
return _foo;
}
}
Because _foo will be nil when the lock is first acquired, no locking will occur and multiple threads can potentially create their own _foo before the first completes.
Using #synchronized to lock on a new object each time.
- (void)foo {
#synchronized([[NSObject alloc] init]) {
[self bar];
}
}
I've seen this code quite a bit, as well as the C# equivalent lock(new object()) {..}. Since it attempts to lock on a new object each time, it will always be allowed into the critical section of code. This is not some kind of code magic. It does absolutely nothing to ensure thread safety.
Lastly, locking on self.
- (void)foo {
#synchronized(self) {
[self bar];
}
}
While not by itself a problem, if your code uses any external code or is itself a library, it can be an issue. While internally the object is known as self, it externally has a variable name. If the external code calls #synchronized(_yourObject) {...} and you call #synchronized(self) {...}, you may find yourself in deadlock. It is best to create an internal object to lock upon that is not exposed outside of your object. Adding _lockObject = [[NSObject alloc] init]; inside your init function is cheap, easy, and safe.
EDIT:
I still get asked questions about this post, so here is an example of why it is a bad idea to use #synchronized(self) in practice.
#interface Foo : NSObject
- (void)doSomething;
#end
#implementation Foo
- (void)doSomething {
sleep(1);
#synchronized(self) {
NSLog(#"Critical Section.");
}
}
// Elsewhere in your code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Foo *foo = [[Foo alloc] init];
NSObject *lock = [[NSObject alloc] init];
dispatch_async(queue, ^{
for (int i=0; i<100; i++) {
#synchronized(lock) {
[foo doSomething];
}
NSLog(#"Background pass %d complete.", i);
}
});
for (int i=0; i<100; i++) {
#synchronized(foo) {
#synchronized(lock) {
[foo doSomething];
}
}
NSLog(#"Foreground pass %d complete.", i);
}
It should be obvious to see why this happens. Locking on foo and lock are called in different orders on the foreground VS background threads. It's easy to say that this is bad practice, but if Foo is a library, the user is unlikely to know that the code contains a lock.
#synchronized alone doesn't make code thread safe but it is one of the tools used in writing thread safe code.
With multi-threaded programs, it's often the case of a complex structure that you want to be maintained in a consistent state and you want only one thread to have access at a time. The common pattern is to use a mutex to protect a critical section of code where the structure is accessed and/or modified.
#synchronized is thread safe mechanism. Piece of code written inside this function becomes the part of critical section, to which only one thread can execute at a time.
#synchronize applies the lock implicitly whereas NSLock applies it explicitly.
It only assures the thread safety, not guarantees that. What I mean is you hire an expert driver for you car, still it doesn't guarantees car wont meet an accident. However probability remains the slightest.
It's companion in GCD(grand central dispatch) is dispatch_once. dispatch_once does the same work as to #synchronized.
The #synchronized directive is a convenient way to create mutex locks on the fly in Objective-C code.
side-effects of mutex locks:
deadlocks
starvation
Thread safety will depend on usage of #synchronized block.
I have researched a ton of posts regarding Core Data on background threads, and I feel like I understand (on paper) what needs to be going on. I guess we'll see. I am working on migrating an existing OS X app to Core Data, and am having issues making new instances of my NSManagedObject on an async thread.
Here is a sample of the code I am running right after I have moved onto a background thread:
NSLog(#"JSON 1");
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[[NSApp delegate] persistentStoreCoordinator]];
asset = (MTAssetInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"Info" inManagedObjectContext:context];
NSLog(#"JSON 2");
The result is that the first log message (#"JSON 1") gets called 31 times, and the second one (#"JSON 2") is never called. The object isn't being made and returned correctly.
The model for this Info entity is quite complex with a few transformable attributes that may or may not be setup correctly. The weird thing is that similar code run on the main thread and the main MOC works great. No issues.
EDIT - Some more context
The async call originates from here:
for (NSNumber *sectionID in sectionsToShow) {
dispatch_group_async(group, queue, ^{
MTAssetInfo *asset = [self assetWithRefID:[sectionID unsignedIntegerValue]];
if (asset != nil) {
[sectionsLock lock];
[sectionsTemp addObject:asset];
[sectionsLock unlock];
}
});
}
The assetWithRefID method never returns with an object because of the other code snippet. It never successfully pulls an NSManagedObject out of the context on the background thread.
You are going to have to provide more information to get real help, but I bet your problem is an error happening in the NSManagedDocument background thread.
I'd register a NSNotificationCenter for ALL messages (name:nil object:nil) and just print them out. I bet you see a status change or error message in there that is failing.
You might want to try a #try/#catch block around it just to see if exceptions are being thrown.
Maybe it will give you more to go on.
One other suggestion... Swizzling isn't necessarily the right tool for production stuff, but it's almost unbeatable for debugging. I have method-swizled several entire classes, so that it sends a detailed NSNotification before/after each invocation.
It has saved me tons of time, and helped me track down some wicked bugs. Now, when something is going on in CoreData, I take out my set of classes, link them in, and see all the detail I want.
I know that does not exactly answer you question, but hopefully it will put you on the track so you can provide some more information and get it all fixed.
If that's too much for you, create a subclass and instantiate that, with a similar method for calling super. You can get a real idea of the entire flow pretty easily.