使用Instruments的CPU strategy view查看代碼如何在多核CPU中執行。建立線程可使用POSIX 線程API,或者NSThread(封裝POSIX 線程API)。下面是併發4個線程在一百萬個數字中找最小值和最大值的pthread例子:php
#import <pthread.h> struct threadInfo { uint32_t * inputValues; size_t count; }; struct threadResult { uint32_t min; uint32_t max; }; void * findMinAndMax(void *arg) { struct threadInfo const * const info = (struct threadInfo *) arg; uint32_t min = UINT32_MAX; uint32_t max = 0; for (size_t i = 0; i < info->count; ++i) { uint32_t v = info->inputValues[i]; min = MIN(min, v); max = MAX(max, v); } free(arg); struct threadResult * const result = (struct threadResult *) malloc(sizeof(*result)); result->min = min; result->max = max; return result; } int main(int argc, const char * argv[]) { size_t const count = 1000000; uint32_t inputValues[count]; // 使用隨機數字填充 inputValues for (size_t i = 0; i < count; ++i) { inputValues[i] = arc4random(); } // 開始4個尋找最小值和最大值的線程 size_t const threadCount = 4; pthread_t tid[threadCount]; for (size_t i = 0; i < threadCount; ++i) { struct threadInfo * const info = (struct threadInfo *) malloc(sizeof(*info)); size_t offset = (count / threadCount) * i; info->inputValues = inputValues + offset; info->count = MIN(count - offset, count / threadCount); int err = pthread_create(tid + i, NULL, &findMinAndMax, info); NSCAssert(err == 0, @"pthread_create() failed: %d", err); } // 等待線程退出 struct threadResult * results[threadCount]; for (size_t i = 0; i < threadCount; ++i) { int err = pthread_join(tid[i], (void **) &(results[i])); NSCAssert(err == 0, @"pthread_join() failed: %d", err); } // 尋找 min 和 max uint32_t min = UINT32_MAX; uint32_t max = 0; for (size_t i = 0; i < threadCount; ++i) { min = MIN(min, results[i]->min); max = MAX(max, results[i]->max); free(results[i]); results[i] = NULL; } NSLog(@"min = %u", min); NSLog(@"max = %u", max); return 0; }
使用NSThread來寫html
@interface FindMinMaxThread : NSThread @property (nonatomic) NSUInteger min; @property (nonatomic) NSUInteger max; - (instancetype)initWithNumbers:(NSArray *)numbers; @end @implementation FindMinMaxThread { NSArray *_numbers; } - (instancetype)initWithNumbers:(NSArray *)numbers { self = [super init]; if (self) { _numbers = numbers; } return self; } - (void)main { NSUInteger min; NSUInteger max; // 進行相關數據的處理 self.min = min; self.max = max; } @end //啓動一個新的線程,建立一個線程對象 SMutableSet *threads = [NSMutableSet set]; NSUInteger numberCount = self.numbers.count; NSUInteger threadCount = 4; for (NSUInteger i = 0; i < threadCount; i++) { NSUInteger offset = (count / threadCount) * i; NSUInteger count = MIN(numberCount - offset, numberCount / threadCount); NSRange range = NSMakeRange(offset, count); NSArray *subset = [self.numbers subarrayWithRange:range]; FindMinMaxThread *thread = [[FindMinMaxThread alloc] initWithNumbers:subset]; [threads addObject:thread]; [thread start]; }
+ (UIColor *)boringColor; { static UIColor *color; //只運行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f]; }); return color; }
使用dispatch_afterios
- (void)foo { double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self bar]; }); }
隊列默認是串行的,只能執行一個單獨的block,隊列也能夠是並行的,同一時間執行多個blockgit
- (id)init; { self = [super init]; if (self != nil) { NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self]; self.isolationQueue = dispatch_queue_create([label UTF8String], 0); label = [NSString stringWithFormat:@"%@.work.%p", [self class], self]; self.workQueue = dispatch_queue_create([label UTF8String], 0); } return self; }
//建立隊列 self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT); //改變setter - (void)setCount:(NSUInteger)count forKey:(NSString *)key { key = [key copy]; //確保全部barrier都是async異步的 dispatch_barrier_async(self.isolationQueue, ^(){ if (count == 0) { [self.counts removeObjectForKey:key]; } else { self.counts[key] = @(count); } }); }
都用異步處理避免死鎖,異步的缺點在於調試不方便,可是比起同步容易產生死鎖這個反作用還算小的。github
設計一個異步的API調用dispatch_async(),這個調用放在API的方法或函數中作。讓API的使用者設置一個回調處理隊列objective-c
- (void)processImage:(UIImage *)image completionHandler:(void(^)(BOOL success))handler; { dispatch_async(self.isolationQueue, ^(void){ // do actual processing here dispatch_async(self.resultQueue, ^(void){ handler(YES); }); }); }
for (size_t y = 0; y < height; ++y) { for (size_t x = 0; x < width; ++x) { // Do something with x and y here } } //使用dispatch_apply能夠運行的更快 dispatch_apply(height, dispatch_get_global_queue(0, 0), ^(size_t y) { for (size_t x = 0; x < width; x += 2) { // Do something with x and y here } });
Block組合
dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_async(group, queue, ^(){ // 會處理一會 [self doSomeFoo]; dispatch_group_async(group, dispatch_get_main_queue(), ^(){ self.foo = 42; }); }); dispatch_group_async(group, queue, ^(){ // 處理一下子 [self doSomeBar]; dispatch_group_async(group, dispatch_get_main_queue(), ^(){ self.bar = 1; }); }); // 上面的都搞定後這裏會執行一次 dispatch_group_notify(group, dispatch_get_main_queue(), ^(){ NSLog(@"foo: %d", self.foo); NSLog(@"bar: %d", self.bar); });
如何對現有API使用dispatch_group_t算法
//給Core Data的-performBlock:添加groups。組合完成任務後使用dispatch_group_notify來運行一個block便可。 - (void)withGroup:(dispatch_group_t)group performBlock:(dispatch_block_t)block { if (group == NULL) { [self performBlock:block]; } else { dispatch_group_enter(group); [self performBlock:^(){ block(); dispatch_group_leave(group); }]; } } //NSURLConnection也能夠這樣作 + (void)withGroup:(dispatch_group_t)group sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler { if (group == NULL) { [self sendAsynchronousRequest:request queue:queue completionHandler:handler]; } else { dispatch_group_enter(group); [self sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){ handler(response, data, error); dispatch_group_leave(group); }]; } }
注意事項
NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"]; if (mail == nil) { return; } pid_t const pid = mail.processIdentifier; self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT); dispatch_source_set_event_handler(self.source, ^(){ NSLog(@"Mail quit."); }); //在事件源傳到你的事件處理前須要調用dispatch_resume()這個方法 dispatch_resume(self.source);
NSURL *directoryURL; // assume this is set to a directory int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY); if (fd < 0) { char buffer[80]; strerror_r(errno, buffer, sizeof(buffer)); NSLog(@"Unable to open "%@": %s (%d)", [directoryURL path], buffer, errno); return; } dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT); dispatch_source_set_event_handler(source, ^(){ unsigned long const data = dispatch_source_get_data(source); if (data & DISPATCH_VNODE_WRITE) { NSLog(@"The directory changed."); } if (data & DISPATCH_VNODE_DELETE) { NSLog(@"The directory has been deleted."); } }); dispatch_source_set_cancel_handler(source, ^(){ close(fd); }); self.source = source; dispatch_resume(self.source); //還要注意須要用DISPATCH_VNODE_DELETE 去檢查監視的文件或文件夾是否被刪除,若是刪除了就中止監聽
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT); dispatch_source_set_event_handler(source, ^(){ NSLog(@"Time flies."); }); dispatch_time_t start dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC,100ull * NSEC_PER_MSEC); self.source = source; dispatch_resume(self.source);
@implementation YourOperation - (void)main { // 進行處理 ... } @end
@implementation YourOperation - (void)start { self.isExecuting = YES; self.isFinished = NO; // 開始處理,在結束時應該調用 finished ... } - (void)finished { self.isExecuting = NO; self.isFinished = YES; } @end //使操做隊列有取消功能,須要不斷檢查isCancelled屬性 - (void)main { while (notDone && !self.isCancelled) { // 進行處理 } }
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; YourOperation *operation = [[YourOperation alloc] init]; [queue addOperation:operation];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 代碼... }];
//確保operation1和operation2是在intermediateOperation和finishOperation以前執行 [intermediateOperation addDependency:operation1]; [intermediateOperation addDependency:operation2]; [finishedOperation addDependency:intermediateOperation];
//weak引用參照self避免循環引用,及block持有self,operationQueue retain了block,而self有retain了operationQueue。 __weak id weakSelf = self; [self.operationQueue addOperationWithBlock:^{ NSNumber* result = findLargestMersennePrime(); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ MyClass* strongSelf = weakSelf; strongSelf.textLabel.text = [result stringValue]; }]; }];
drawRect:方法會影響性能,因此能夠放到後臺執行。編程
//使用UIGraphicsBeginImageContextWithOptions取代UIGraphicsGetCurrentContext:方法 UIGraphicsBeginImageContextWithOptions(size, NO, 0); // drawing code here UIImage *i = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return i;
能夠把這個方法運用到table view中,使table view的cell在滾出邊界時能在didEndDisplayingCell委託方法中取消。WWDC中有講解:Session 211 -- Building Concurrent User Interfaces on iOS https://developer.apple.com/videos/wwdc/2012/api
還有個使用CALayer裏drawsAsynchronously屬性的方法。不過有時work,有時不必定。安全
網絡都要使用異步方式,可是不要直接使用dispatch_async,這樣無法取消這個網絡請求。dataWithContentsOfURL:的超時是30秒,那麼這個線程須要乾等到超時完。解決辦法就是使用NSURLConnection的異步方法,把全部操做轉化成operation來執行。NSURLConnection是經過run loop來發送事件的。AFNetworking是創建一個獨立的線程設置一個非main run loop。下面是處理URL鏈接重寫自定義operation子類裏的start方法
- (void)start { NSURLRequest* request = [NSURLRequest requestWithURL:self.url]; self.isExecuting = YES; self.isFinished = NO; [[NSOperationQueue mainQueue] addOperationWithBlock:^ { self.connection = [NSURLConnectionconnectionWithRequest:request delegate:self]; }]; }
重寫start方法須要管理isExecuting和isFinished狀態。下面是取消操做的方法
- (void)cancel { [super cancel]; [self.connection cancel]; self.isFinished = YES; self.isExecuting = NO; } //鏈接完成發送回調 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { self.data = self.buffer; self.buffer = nil; self.isExecuting = NO; self.isFinished = YES; }
異步處理文件可使用NSInputStream。官方文檔:http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/TechniquesforReadingandWritingCustomFiles.html 實例:https://github.com/objcio/issue-2-background-file-io
@interface Reader : NSObject - (void)enumerateLines:(void (^)(NSString*))block completion:(void (^)())completion; - (id)initWithFileAtPath:(NSString*)path; //採用main run loop的事件將數據發到後臺操做線程去處理 - (void)enumerateLines:(void (^)(NSString*))block completion:(void (^)())completion { if (self.queue == nil) { self.queue = [[NSOperationQueue alloc] init]; self.queue.maxConcurrentOperationCount = 1; } self.callback = block; self.completion = completion; self.inputStream = [NSInputStream inputStreamWithURL:self.fileURL]; self.inputStream.delegate = self; [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [self.inputStream open]; } @end //input stream在主線程中發送代理消息,接着就能夠在操做隊列加入block操做 - (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode { switch (eventCode) { ... case NSStreamEventHasBytesAvailable: { NSMutableData *buffer = [NSMutableData dataWithLength:4 * 1024]; NSUInteger length = [self.inputStream read:[buffer mutableBytes] maxLength:[buffer length]]; if (0 < length) { [buffer setLength:length]; __weak id weakSelf = self; [self.queue addOperationWithBlock:^{ [weakSelf processDataChunk:buffer]; }]; } break; } ... } } //處理數據chunk,原理就是把數據切成不少小塊,而後不斷更新和處理buffer緩衝區,逐塊讀取和存入方式來處理大文件響應快並且內存開銷也小。 - (void)processDataChunk:(NSMutableData *)buffer; { if (self.remainder != nil) { [self.remainder appendData:buffer]; } else { self.remainder = buffer; } [self.remainder obj_enumerateComponentsSeparatedBy:self.delimiter usingBlock:^(NSData* component, BOOL last) { if (!last) { [self emitLineWithData:component]; } else if (0 < [component length]) { self.remainder = [component mutableCopy]; } else { self.remainder = nil; } }]; }
併發開發會遇到的困難問題
好比兩個線程都會把計算結果寫到一個整型數中。爲了防止,須要一種互斥機制來訪問共享資源
同一時刻只能有一個線程訪問某個資源。某線程要訪問某個共享資源先得到共享資源的互斥鎖,完成操做再釋放這個互斥鎖,而後其它線程就能訪問這個共享資源。
還有須要解決無序執行問題,這時就須要引入內存屏障。
在Objective-C中若是屬性聲明爲atomic就可以支持互斥鎖,可是由於加解鎖會有性能代價,因此通常是聲明noatomic的。
當多個線程在相互等待對方鎖結束時就會發生死鎖,程序可能會卡住。
void swap(A, B) { lock(lockA); lock(lockB); int a = A; int b = B; A = b; B = a; unlock(lockB); unlock(lockA); }
//通常沒問題,可是若是兩個線程使用相反的值同時調用上面這個方法就可能會死鎖。線程1得到X的一個鎖,線程2得到Y的一個鎖,它們會同時等待另外一個鎖的釋放,可是倒是無法等到的。 swap(X, Y); // 線程 1 swap(Y, X); // 線程 2
爲了防止死鎖,須要使用比簡單讀寫鎖更好的辦法,好比write preferencehttp://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock,或read-copy-update算法http://en.wikipedia.org/wiki/Read-copy-update
運行時低優先級任務因爲先取得了釋放了鎖的共享資源而阻塞了高優先級任務,這種狀況叫作優先級反轉
從主線程中取到數據,利用一個操做隊列在後臺處理數據,完後返回後臺隊列中獲得的數據到主隊列中。這樣的操做不會有任何鎖操做。