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

ITT 2014 - Chris Eidhof - Practical Concurrent Programming

ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Istanbul Tech Talks

May 12, 2014
Tweet

More Decks by Istanbul Tech Talks

Other Decks in Programming

Transcript

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

    background thread 3. Update the main thread
  2. Moving it to a different thread [queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO,

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

    expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = i; }]; }];
  4. [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); }];
  5. Solution NSDictionary *options = @{kCIContextUseSoftwareRenderer: @YES}; CIContext *context = [CIContext

    contextWithCGContext:cgContext options:options]; ... and ... self.queue.maxConcurrentOperationCount = 1;
  6. 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; }); });
  7. 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
  8. dispatch_async(backgroundQueue, ^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image =

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

    NSOperation* drawingOperation = [DrawingOperation drawingOperationWithData:data]; drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }]; };
  13. 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
  14. Importing employees for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier;

    Employee *employee = [self findEmployeeWithIdentifier:identifier]; if (employee == nil) { employee = [self createEmployeWithIdentifier:identifier]; } // Update data }
  15. 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];
  16. [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) { NSManagedObjectContext *moc

    = self.mainMOC; if (note.object != moc) { [moc performBlock:^(){ [moc mergeChangesFromContextDidSaveNotification:note]; }]; }; }];
  17. 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
  18. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! !

    ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! ! !
  19. SQLite ! NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext NSPersistentStore NSPersistentStoreCoordinator NSManagedObjectContext ! !

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

    shipped the sqlite file for the initial import.
  21. - (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?
  22. 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; }
  23. 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
  24. 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
  25. - (void)transfer:(double)euros to:(Account*)other { @synchronized(self) { @synchronized(other) { self.balance =

    self.balance - euros; other.balance = other.balance + euros; } } } Problem: deadlock.
  26. 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.
  27. 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
  28. 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
  29. 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