69
Practical Concurrent Programming Chris Eidhof

ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Embed Size (px)

Citation preview

Page 1: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Practical Concurrent ProgrammingChris Eidhof

Page 2: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Merhabā

Page 3: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 4: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Why is concurrency hard?

Page 5: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 6: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 7: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 8: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 9: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

When to use concurrency?

Always use the main thread

Page 10: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

When to use concurrency?

— Networking

— Expensive stuff

Page 11: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Decision workflow

1. Measure

2. Change

3. Measure again

Page 12: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

How to draw things in the background

Page 13: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 14: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Recipe

1. Take drawRect: code

2. Put it in a background thread

3. Update the main thread

Page 15: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

The drawRect: code

- (void)drawRect:(CGRect)rect{ CGContextRef ctx = UIGraphicsGetCurrentContext(); // expensive // drawing // code}

Page 16: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Moving it to a different thread

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

Page 17: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Updating the main thread

[queue addOperationWithBlock:^{ UIGraphicsBeginImageContextWithOptions(size, NO, 0); // expensive // drawing // code UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = i; }];}];

Page 18: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Deckset filters

Page 19: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 20: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

[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);}];

Page 21: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

... the shared resource was the GPU!

Page 22: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Solution

NSDictionary *options = @{kCIContextUseSoftwareRenderer: @YES};CIContext *context = [CIContext contextWithCGContext:cgContext options:options];

... and ...

self.queue.maxConcurrentOperationCount = 1;

Page 23: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Solution, part 2

... we removed the complicated filter

Page 24: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 25: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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; });});

Page 26: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Grand Central Dispatch

Page 27: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 28: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 29: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Threading is hardGCD moves it to the system-level

Page 30: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

GCD Thread PoolMain Thread

High Priority Queue

SerialQueue

ParallelQueue

Serial Queue

MainQueue

SerialQueue

ConcurrentQueue

SerialQueue

Default Priority Queue

Low Priority Queue

Background Priority Queue

Custom Queues

GCD Queues

Threads

Page 31: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

— Simpler

— Faster

— Thread-pool management

— Memory-efficient

— Async means: no deadlock!

Page 32: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

How to load things from the network

Use NSURLSession

Page 33: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Operation Queues

Page 34: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

dispatch_async(backgroundQueue, ^{ NSArray *graphData = [self generateDataPointsForGraph]; UIImage* image = [self drawGraph:graphData]; dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; });});

Page 35: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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?

Page 36: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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];

Page 37: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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; }];};

Page 38: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Step 4: Custom NSOperation subclass

NSData *data = [self generateDataPointsForGraph];NSOperation* drawingOperation = [DrawingOperation drawingOperationWithData:data];drawingOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.imageView.image = image; }];};

Page 39: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

How to efficiently import data into Core Data

Page 40: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 41: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 42: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Importing employees

for (WebserviceEmployee *webserviceEmployee) { NSNumber *identifier = webserviceEmployee.identifier; Employee *employee = [self findEmployeeWithIdentifier:identifier]; if (employee == nil) { employee = [self createEmployeWithIdentifier:identifier]; } // Update data}

Page 43: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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];

Page 44: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Import in batchesUse a separate context

Page 45: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Normal Core Data Stack

Page 46: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

NSManagedObjectContextNSManagedObjectContext NSManagedObjectContext

SQLite

NSPersistentStore

NSPersistentStoreCoordinator

Double MOC Stack

Page 47: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Small imports

NSManagedObjectContext* context = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];context.persistentStoreCoordinator = self.ptStoreCoordinator;[self.context performBlock:^{ [self import];}];

Page 48: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note){ NSManagedObjectContext *moc = self.mainMOC; if (note.object != moc) { [moc performBlock:^(){ [moc mergeChangesFromContextDidSaveNotification:note]; }]; };}];

Page 49: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

NSManagedObjectContextNSManagedObjectContext NSManagedObjectContext!

SQLite

NSPersistentStore

NSPersistentStoreCoordinator!

!

!

Double MOC Stack

Page 50: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 51: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

SQLite!

NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext

NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext!

!

!NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext

NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext!

!

!

Page 52: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

SQLite!

NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext

NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext!

!

!NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext

NSPersistentStore

NSPersistentStoreCoordinator

NSManagedObjectContext!

!

!

NSManagedObjectContextNSManagedObjectContext NSManagedObjectContext!

SQLite

NSPersistentStore

NSPersistentStoreCoordinator!

!

!

Page 53: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Importing Recap

Didn't turn out to be a problem: we shipped the sqlite file for the initial import.

Page 54: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

How to make objects play well when concurrent

Page 55: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

@interface Account : NSObject

@property (nonatomic) double balance;

- (void)transfer:(double)euros to:(Account*)other;

@end

Page 56: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

- (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?

Page 57: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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;}

Page 58: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 59: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 60: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 61: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

}}

Problem: deadlock.

Page 62: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 63: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Solution: move concurrency to a different level

Page 64: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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.

Page 65: ITT 2014 - Chris Eidhof - Practical Concurrent Programming
Page 66: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 67: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

Thanks

— @chriseidhof

— http://www.objc.io

— http://www.uikonf.com

— http://www.decksetapp.com

Page 68: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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

Page 69: ITT 2014 - Chris Eidhof - Practical Concurrent Programming

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