Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Practical Concurrent Programming

Practical Concurrent Programming

Istanbul Tech Talks

Transcript

  1. Practical Concurrent Programming Chris Eidhof

  2. Merhabā

  3. None
  4. Why is concurrency hard?

  5. None
  6. None
  7. None
  8. None
  9. When to use concurrency? Always use the main thread

  10. When to use concurrency? — Networking — Expensive stuff

  11. Decision workflow 1. Measure 2. Change 3. Measure again

  12. How to draw things in the background

  13. None
  14. Recipe 1. Take drawRect: code 2. Put it in a

    background thread 3. Update the main thread
  15. The drawRect: code - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext();

    // expensive // drawing // code }
  16. Moving it to a different thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO,

    0); // expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // TODO: update the main thread }];
  17. Updating the main thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); //

    expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = i; }]; }];
  18. Deckset filters

  19. None
  20. [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); }];
  21. ... the shared resource was the GPU!

  22. Solution NSDictionary *options = @{kCIContextUseSoftwareRenderer: @YES}; CIContext *context = [CIContext

    contextWithCGContext:cgContext options:options]; ... and ... self.queue.maxConcurrentOperationCount = 1;
  23. Solution, part 2 ... we removed the complicated filter

  24. None
  25. 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; }); });
  26. Grand Central Dispatch

  27. None
  28. None
  29. Threading is hard GCD moves it to the system- level

  30. 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
  31. — Simpler — Faster — Thread-pool management — Memory-efficient —

    Async means: no deadlock!
  32. How to load things from the network Use NSURLSession

  33. Operation Queues

  34. dispatch_async(backgroundQueue, ^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image =

    [self drawGraph:graphData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
  35. 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?
  36. 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];
  37. 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; }]; };
  38. Step 4: Custom NSOperation subclass NSData *data = [self generateDataPointsForGraph];

    NSOperation* drawingOperation = [DrawingOperation drawingOperationWithData:data]; drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; };
  39. How to efficiently import data into Core Data

  40. None
  41. 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
  42. Importing employees for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier;

    Employee *employee = [self findEmployeeWithIdentifier:identifier]; if (employee == nil) { employee = [self createEmployeWithIdentifier:identifier]; } // Update data }
  43. 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];
  44. Import in batches Use a separate context

  45. Normal Core Data Stack

  46. NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext SQLite NSPersistentStore NSPersistentStoreCoordinator Double MOC Stack

  47. Small imports NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; context.persistentStoreCoordinator =

    self.ptStoreCoordinator; [self.context performBlock:^{ [self import]; }];
  48. [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) { NSManagedObjectContext *moc

    = self.mainMOC; if (note.object != moc) { [moc performBlock:^(){ [moc mergeChangesFromContextDidSaveNotification:note]; }]; }; }];
  49. NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext ! SQLite NSPersistentStore NSPersistentStoreCoordinator ! ! !

    Double MOC Stack
  50. 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
  51. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! !

    ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! !
  52. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! !

    ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! ! NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext ! SQLite NSPersistentStore NSPersistentStoreCoordinator ! ! !
  53. Importing Recap Didn't turn out to be a problem: we

    shipped the sqlite file for the initial import.
  54. How to make objects play well when concurrent

  55. @interface Account : NSObject @property (nonatomic) double balance; - (void)transfer:(double)euros

    to:(Account*)other; @end
  56. - (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?
  57. 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; }
  58. 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
  59. 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
  60. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self) { self.balance = self.balance -

    euros; other.balance = other.balance + euros; } }
  61. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self) { @synchronized(other) { self.balance =

    self.balance - euros; other.balance = other.balance + euros; } } } Problem: deadlock.
  62. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self.class) { self.balance = self.balance -

    euros; other.balance = other.balance + euros; } }
  63. Solution: move concurrency to a different level

  64. 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.
  65. None
  66. 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
  67. Thanks — @chriseidhof — http://www.objc.io — http://www.uikonf.com — http://www.decksetapp.com

  68. 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
  69. 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