Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

ITT 2014 - Chris Eidhof - Practical Concurrent Programming

162 views

Published on

Chris highlights the benefits and different techniques of doing concurrent programming and show how developers can avoid some obvious and some not so obvious mistakes.

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

ITT 2014 - Chris Eidhof - Practical Concurrent Programming

  1. 1. Practical Concurrent Programming Chris Eidhof
  2. 2. Merhabā
  3. 3. Why is concurrency hard?
  4. 4. When to use concurrency? Always use the main thread
  5. 5. When to use concurrency? — Networking — Expensive stuff
  6. 6. Decision workflow 1. Measure 2. Change 3. Measure again
  7. 7. How to draw things in the background
  8. 8. Recipe 1. Take drawRect: code 2. Put it in a background thread 3. Update the main thread
  9. 9. The drawRect: code - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); // expensive // drawing // code }
  10. 10. Moving it to a different thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); // expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // TODO: update the main thread }];
  11. 11. Updating the main thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); // expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = i; }]; }];
  12. 12. Deckset filters
  13. 13. [self.queue addOperationWithBlock:^{ CIImage *ciImage = [CIImage imageWithContentsOfURL:sourceURL]; CIFilter *depthOfFieldFilter = [CIFilter filterWithName:@"CIDepthOfField"]; ... CIImage *finalImage = [alphaFilter valueForKey:kCIOutputImageKey]; CGContextRef cgContext = CGBitmapContextCreate(NULL, size.width, size.height, 8, size.width * 4, colorSpace, kCGImageAlphaPremultipliedLast); CIContext *context = [CIContext contextWithCGContext:cgContext options:nil]; CGImageRef outputImage = [context createCGImage:finalImage fromRect:ciImage.extent]; ... CGImageDestinationAddImage(destination, outputImage, nil); CGImageDestinationFinalize(destination); }];
  14. 14. ... the shared resource was the GPU!
  15. 15. Solution NSDictionary *options = @{kCIContextUseSoftwareRenderer: @YES}; CIContext *context = [CIContext contextWithCGContext:cgContext options:options]; ... and ... self.queue.maxConcurrentOperationCount = 1;
  16. 16. Solution, part 2 ... we removed the complicated filter
  17. 17. How to not load things from the network dispatch_async(backgroundQueue, ^{ NSData *data = [NSData dataWithContentsOfURL:url]; NSArray *graphItems = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; UIImage* image = [self drawGraph:graphItems]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
  18. 18. Grand Central Dispatch
  19. 19. Threading is hard GCD moves it to the system- level
  20. 20. GCD Thread Pool Main Thread High Priority Queue Serial Queue Parallel Queue Serial Queue Main Queue Serial Queue Concurrent Queue Serial Queue Default Priority Queue Low Priority Queue Background Priority Queue Custom Queues GCD Queues Threads
  21. 21. — Simpler — Faster — Thread-pool management — Memory-efficient — Async means: no deadlock!
  22. 22. How to load things from the network Use NSURLSession
  23. 23. Operation Queues
  24. 24. dispatch_async(backgroundQueue, ^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
  25. 25. Step 1: Use NSOperation NSOperationQueue* drawingQueue = [[NSOperationQueue alloc] init]; [drawingQueue addOperationWithBlock:^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; How to cancel this?
  26. 26. Step 2: Use NSBlockOperation NSBlockOperation* drawingOperation = [NSBlockOperation blockOperationWithBlock:^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; }]; [drawingQueue addOperation:drawingOperation];
  27. 27. Step 3: Pull out the completion handler NSOperation* drawingOperation = [NSBlockOperation blockOperationWithBlock:^{ NSArray *graphData = [self generateDataPointsForGraph]; self.image = [self drawGraph:graphData]; }]; drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = self.image; }]; };
  28. 28. Step 4: Custom NSOperation subclass NSData *data = [self generateDataPointsForGraph]; NSOperation* drawingOperation = [DrawingOperation drawingOperationWithData:data]; drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; };
  29. 29. How to efficiently import data into Core Data
  30. 30. Efficiently Importing Data — Implement Find-or-Create Efficiently — Reduce Peak Memory Footprint https://developer.apple.com/library/ios/ documentation/Cocoa/Conceptual/CoreData/ Articles/cdImporting.html
  31. 31. Importing employees for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier; Employee *employee = [self findEmployeeWithIdentifier:identifier]; if (employee == nil) { employee = [self createEmployeWithIdentifier:identifier]; } // Update data }
  32. 32. Importing more efficiently NSMutableArray *identifiers = [NSMutableArray array]; for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier; [identifiers addObject:identifier]; } NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(employeeID IN %@)", identifiers];
  33. 33. Import in batches Use a separate context
  34. 34. Normal Core Data Stack
  35. 35. NSManagedObjectContextNSManagedObjectContext NSManagedObjectContext SQLite NSPersistentStore NSPersistentStoreCoordinator Double MOC Stack
  36. 36. Small imports NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; context.persistentStoreCoordinator = self.ptStoreCoordinator; [self.context performBlock:^{ [self import]; }];
  37. 37. [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) { NSManagedObjectContext *moc = self.mainMOC; if (note.object != moc) { [moc performBlock:^(){ [moc mergeChangesFromContextDidSaveNotification:note]; }]; }; }];
  38. 38. NSManagedObjectContextNSManagedObjectContext NSManagedObjectContext ! SQLite NSPersistentStore NSPersistentStoreCoordinator ! ! ! Double MOC Stack
  39. 39. You might want to consider using a different concurrency style, and this time you have two persistent store coordinators, two almost completely separate Core Data stacks. Source: http://asciiwwdc.com/2013/sessions/ 211
  40. 40. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! !
  41. 41. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSManagedObjectContextNSManagedObjectContext NSManagedObjectContext ! SQLite NSPersistentStore NSPersistentStoreCoordinator ! ! !
  42. 42. Importing Recap Didn't turn out to be a problem: we shipped the sqlite file for the initial import.
  43. 43. How to make objects play well when concurrent
  44. 44. @interface Account : NSObject @property (nonatomic) double balance; - (void)transfer:(double)euros to:(Account*)other; @end
  45. 45. - (void)transfer:(double)euros to:(Account*)other { self.balance = self.balance - euros; other.balance = other.balance + euros; } What happens if two methods call this method at the same time? From different threads?
  46. 46. The same code, how the compiler sees it - (void)transfer:(double)euros to:(Account*)other { double currentBalance = self.balance; self.balance = currentBalance - euros; double otherBalance = other.balance; other.balance = otherBalance + euros; }
  47. 47. a b [a.transfer:20 to:b] [a.transfer:30 to:b] 100 0 currentBalance = 100 currentBalance = 100 100 0 a.balance = 100 - 20 80 0 b.balance = b.balance + 20 80 20 a.balance = 100 - 30 70 20
  48. 48. a b [a.transfer:20 to:b] [a.transfer:30 to:b] 100 0 currentBalance = 100 currentBalance = 100 100 0 a.balance = 100 - 20 80 0 b.balance = b.balance + 20 80 20 a.balance = 100 - 30 70 20
  49. 49. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self) { self.balance = self.balance - euros; other.balance = other.balance + euros; } }
  50. 50. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self) { @synchronized(other) { self.balance = self.balance - euros; other.balance = other.balance + euros; } } } Problem: deadlock.
  51. 51. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self.class) { self.balance = self.balance - euros; other.balance = other.balance + euros; } }
  52. 52. Solution: move concurrency to a different level
  53. 53. Do it the GCD way Account* account = [Account new]; Account* other = [Account new]; dispatch_queue_t accountOperations = dispatch_queue_create("accounting", DISPATCH_QUEUE_SERIAL); dispatch_async(accountOperations, ^{ [account transfer:200 to:other]; }); dispatch_async will never block.
  54. 54. There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult. — Tony Hoare
  55. 55. Thanks — @chriseidhof — http://www.objc.io — http://www.uikonf.com — http://www.decksetapp.com
  56. 56. Resources — Concurrency Programming Guide — Threading Programming Guide — NSOperationQueue class reference — http://www.objc.io/issue-2/ — http://www.opensource.apple.com/source/ objc4/objc4-551.1/runtime/objc-sync.mm — http://googlemac.blogspot.de/2006/10/ synchronized-swimming.html — WWDC12 #211: Concurrent User Interfaces on iOS
  57. 57. Icons are from the Noun Project: — Coffee Maker by Maureen Placente — Coffee by Julia Soderberg — Railroad Crossing by Edward Boatman — Database by Stefan Parnarov — Drawing by Daniel Shannon — Hammer by Alex AS — Lock by P.J. Onori — Photoshop by Joe Harrison — Register by Wilson Joseph

×