线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行。创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API)。下面是并发4个线程在一百万个数字中找最小值和最大值的pthread例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #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]; for (size_t i = 0 ; i < count; ++i) { inputValues[i] = arc4random(); } 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); } 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来写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 @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]; }
Grand Central Dispatch GCD中的FIFO队列称为dispatch queue,用来保证先进来的任务先得到执行。
dispatch queue分三种
Serial:又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
Concurrent:又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。
Main dispatch queue:全局可用的serial queue,在应用程序主线程上执行任务。
GCD概要
和operation queue一样都是基于队列的并发编程API,他们通过集中管理大家协同使用的线程池。
公开的5个不同队列:运行在主线程中的main queue,3个不同优先级的后台队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue(用于I/O)
可创建自定义队列:串行或并列队列。自定义一般放在Default Priority Queue和Main Queue里。
dispatch_once用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + (UIColor *)boringColor; { static UIColor *color ; static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ color = [UIColor colorWithRed:0.380 f green :0.376 f blue :0.376 f alpha :1.000 f]; }); return color ; }
dispatch_async(较常用) 可以避免界面会被一些耗时的操作卡死,比如读取网络数据,大数据IO,还有大量数据的数据库读写,这时需要在另一个线程中处理,然后通知主线程更新界面,GCD使用起来比NSThread和NSOperation方法要简单方便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ dispatch_async (dispatch_get_main_queue(), ^{ }); }); dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg" ]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [[UIImage alloc]initWithData:data]; if (data != nil ) { dispatch_async (dispatch_get_main_queue(), ^{ self .imageView.image = image; }); } });
dispatch_after延后执行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - (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]; }); }
范例,实现一个推迟出现弹出框提示,比如说提示用户评价等功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func showOrHideNavPrompt () { let delayInSeconds = 1.0 let popTime = dispatch_time(DISPATCH_TIME_NOW , Int64 (delayInSeconds * Double (NSEC_PER_SEC ))) dispatch_after(popTime, GlobalMainQueue ) { let count = PhotoManager .sharedManager.photos.count if count > 0 { self .navigationItem.prompt = nil } else { self .navigationItem.prompt = "Add photos with faces to Googlyify them!" } } }
GCD队列 队列默认是串行的,只能执行一个单独的block,队列也可以是并行的,同一时间执行多个block
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 - (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 ; }
5种队列,主队列(main queue),四种通用调度队列,自己定制的队列。四种通用调度队列为
QOS_CLASS_USER_INTERACTIVE:user interactive等级表示任务需要被立即执行提供好的体验,用来更新UI,响应事件等。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
示例:后台加载显示图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 override func viewDidLoad ( ) { super .viewDidLoad () dispatch_async (dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0 ) ) { let overlayImage = self.faceOverlayImageFromImage (self.image ) dispatch_async (dispatch_get_main_queue() ) { self.fadeInNewImage (overlayImage) } } }
何时使用何种队列类型
主队列(顺序):队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。
可以使用下面的方法简化QoS等级参数的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 var GlobalMainQueue: dispatch_queue_t { return dispatch_get_main_queue () } var GlobalUserInteractiveQueue: dispatch_queue_t { return dispatch_get_global_queue (Int (QOS_CLASS_USER_INTERACTIVE.value), 0 ) } var GlobalUserInitiatedQueue: dispatch_queue_t { return dispatch_get_global_queue (Int (QOS_CLASS_USER_INITIATED.value), 0 ) } var GlobalUtilityQueue: dispatch_queue_t { return dispatch_get_global_queue (Int (QOS_CLASS_UTILITY.value), 0 ) } var GlobalBackgroundQueue: dispatch_queue_t { return dispatch_get_global_queue (Int (QOS_CLASS_BACKGROUND.value), 0 ) } dispatch_async (GlobalUserInitiatedQueue) { let overlayImage = self.faceOverlayImageFromImage (self.image) dispatch_async (GlobalMainQueue) { self.fadeInNewImage (overlayImage) } }
使用Barrier Task方法Dispatch Barrier解决多线程并发读写同一个资源发生死锁 Dispatch Barrier确保提交的闭包是指定队列中在特定时段唯一在执行的一个。在所有先于Dispatch Barrier的任务都完成的情况下这个闭包才开始执行。轮到这个闭包时barrier会执行这个闭包并且确保队列在此过程不会执行其它任务。闭包完成后队列恢复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 self .isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);- (void )setCount:(NSUInteger )count forKey:(NSString *)key { key = [key copy ]; dispatch_barrier_async(self .isolationQueue, ^(){ if (count == 0 ) { [self .counts removeObjectForKey:key]; } else { self .counts[key] = @(count); } }); }
swift示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private let concurrentPhotoQueue = dispatch_queue_create ("com.raywenderlich.GooglyPuff.photoQueue" , DISPATCH_QUEUE_CONCURRENT )func addPhoto (photo : Photo ) { dispatch_barrier_async (concurrentPhotoQueue ) { self ._photos .append (photo ) dispatch_async (GlobalMainQueue ) { self .postContentAddedNotification () } } } var photos : [Photo ] { var photosCopy : [Photo ]! dispatch_sync (concurrentPhotoQueue ) { photosCopy = self ._photos } return photosCopy }
都用异步处理避免死锁,异步的缺点在于调试不方便,但是比起同步容易产生死锁这个副作用还算小的。
异步API写法 设计一个异步的API调用dispatch_async(),这个调用放在API的方法或函数中做。让API的使用者设置一个回调处理队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - (void )processImage:(UIImage *)image completionHandler:(void (^)(BOOL success))handler; { dispatch_async (self .isolationQueue, ^(void ){ dispatch_async (self .resultQueue, ^(void ){ handler(YES ); }); }); }
dispatch_apply进行快速迭代 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 for (size_t y = 0 ; y < height; ++y ) { for (size_t x = 0 ; x < width; ++x ) { } } dispatch_apply(height, dispatch_get_global_queue(0 , 0 ), ^(size_t y ) { for (size_t x = 0 ; x < width; x += 2 ) { } });
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { var storedError: NSError! var downloadGroup = dispatch_group_create() let addresses = [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] dispatch_apply(UInt(addresses.count), GlobalUserInitiatedQueue) { I in let index = Int(i) let address = addresses[index] let url = NSURL(string: address) dispatch_group_enter(downloadGroup) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto(photo) } dispatch_group_notify(downloadGroup, GlobalMainQueue) { if let completion = completion { completion(error: storedError) } } }
Block组合Dispatch_groups dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。
当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。
第一种使用dispatch_group_wait的swift的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { dispatch_async(GlobalUserInitiatedQueue) { // 因为dispatch_group_wait会租塞当前进程,所以要使用dispatch_async将整个方法要放到后台队列才能够保证主线程不被阻塞 var storedError: NSError! var downloadGroup = dispatch_group_create() // 创建一个dispatch group for address in [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] { let url = NSURL(string: address) dispatch_group_enter(downloadGroup) // dispatch_group_enter是通知dispatch group任务开始了,dispatch_group_enter和dispatch_group_leave是成对调用,不然程序就崩溃了。 let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) // 保持和dispatch_group_enter配对。通知任务已经完成 } PhotoManager.sharedManager.addPhoto(photo) } dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // dispatch_group_wait等待所有任务都完成直到超时。如果任务完成前就超时了,函数会返回一个非零值,可以通过返回值判断是否超时。也可以用DISPATCH_TIME_FOREVER表示一直等。 dispatch_async(GlobalMainQueue) { // 这里可以保证所有图片任务都完成,然后在main queue里加入完成后要处理的闭包,会在main queue里执行。 if let completion = completion { // 执行闭包内容 completion(error: storedError) } } } }
第二种使用dispatch_group_notify的swift的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { // 不用加dispatch_async,因为没有阻塞主进程 var storedError: NSError! var downloadGroup = dispatch_group_create() for address in [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] { let url = NSURL(string: address) dispatch_group_enter(downloadGroup) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto(photo) } dispatch_group_notify(downloadGroup, GlobalMainQueue) { // dispatch_group_notify和dispatch_group_wait的区别就是是异步执行闭包的,当dispatch groups中没有剩余的任务时闭包才执行。这里是指明在主队列中执行。 if let completion = completion { completion(error: storedError) } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 //给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); }]; } }
注意事项
dispatch_group_enter() 必须运行在 dispatch_group_leave() 之前。
dispatch_group_enter() 和 dispatch_group_leave() 需要成对出现的
在任务执行前进行取消 dispatch block object可以为队列中的对象设置 示例,下载图片中途进行取消
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { var storedError: NSError! let downloadGroup = dispatch_group_create() var addresses = [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] addresses += addresses + addresses // 扩展address数组,复制3份 var blocks: [dispatch_block_t] = [] // 一个保存block的数组 for i in 0 ..< addresses.count { dispatch_group_enter(downloadGroup) let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) { // 创建一个block,block的标志是DISPATCH_BLOCK_INHERIT_QOS_CLASS let index = Int(i) let address = addresses[index] let url = NSURL(string: address) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto(photo) } blocks.append(block) dispatch_async(GlobalMainQueue, block) // 把这个block放到GlobalMainQueue上异步调用。因为全局队列是一个顺序队列所以方便取消对象block,同时可以保证下载任务在downloadPhotosWithCompletion返回后才开始执行。 } for block in blocks[3 ..< blocks.count] { let cancel = arc4random_uniform(2) // 随机返回一个整数,会返回0或1 if cancel == 1 { dispatch_block_cancel(block) // 如果是1就取消block,这个只能发生在block还在队列中并没有开始的情况下。因为把block已经放到了GlobalMainQueue中,所以这个地方会先执行,执行完了才会执行block。 dispatch_group_leave(downloadGroup) // 因为已经dispatch_group_enter了,所以取消时也要将其都leave掉。 } } dispatch_group_notify(downloadGroup, GlobalMainQueue) { if let completion = completion { completion(error: storedError) } } }
用GCD监视进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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(self .source);
监视文件夹内文件变化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 NSURL *directoryURL; 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);
GCD版定时器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 startdispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5 ull * NSEC_PER_SEC,100 ull * NSEC_PER_MSEC);self.source = source; dispatch_resume(self.source);
GCD深入操作
缓冲区:dispatch_data_t基于零碎的内存区域,使用dispatch_data_apply来遍历,还可以用dispatch_data_create_subrange来创建一个不做任何拷贝的子区域
I/O调度:使用GCD提供的dispatch_io_read,dispatch_io_write和dispatch_io_close
测试:使用dispatch_benchmark小工具
原子操作: libkern/OSAtomic.h里可以查看那些函数,用于底层多线程编程。
Cocoa NSOperation 使用NSOperation的两种方式。一种用定义好的两个子类NSInvocationOperation和NSBlockOperation,另一个是继承NSOperation。
NSOperation是设计用来扩展的,只需继承重写NSOperation的一个方法main,然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。
NSInvocationOperation例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #import "ViewController.h" #define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg" @interface ViewController ()@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector (downloadImage:) object:kURL]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:operation]; } -(void )downloadImage:(NSString *)url{ NSLog (@"url:%@" , url); NSURL *nsUrl = [NSURL URLWithString:url]; NSData *data = [[NSData alloc]initWithContentsOfURL:nsUrl]; UIImage * image = [[UIImage alloc]initWithData:data]; [self performSelectorOnMainThread:@selector (updateUI:) withObject:image waitUntilDone:YES ]; } -(void )updateUI:(UIImage *) image{ self .imageView.image = image; }
Operation Queue是在GCD上实现了一些方便的功能。优点是不需要关心线程管理,数据同步。Cocoa operation相关的类是NSOperation,NSOperationQueue。
NSOperationQueue有主队列和自定义队列两种类型队列。主队列在主线程上运行,自定义队列在后台。
重写main方法自定义自己的operations。较简单,不需要管理isExecuting和isFinished,main返回时operation就结束了。
1 2 3 4 5 6 7 8 9 10 11 12 13 @implementation YourOperation - (void)main { } @end
重写start方法能够获得更多的控制权,还可以在一个操作中执行异步任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @implementation YourOperation - (void )start { self .isExecuting = YES ; self .isFinished = NO ; } - (void )finished { self .isExecuting = NO ; self .isFinished = YES ; } @end - (void )main { while (notDone && !self .isCancelled) { } }
1 2 3 * 定义好operation类以后,将一个operation加到队列里:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
YourOperation *operation = [[YourOperation alloc] init];
[queue addOperation:operation];
1 2 3 * 如果是在主队列中进行一个一次性任务,可以将block加到操作队列
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 代码...
}];
1 2 3 4 * 通过maxConcurrentOperationCount属性控制一个特定队列中并发执行操作的数量。设置为1就是串行队列。* 对operation优先级排序,指定operation之间的依赖关系。
//确保operation1和operation2是在intermediateOperation和finishOperation之前执行
[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
[finishedOperation addDependency:intermediateOperation];
1 2 3 4 5 6 7 8 9 10 11 # NSThread NSThread抽象度最高,也是Apple所推荐的。缺点是需要自己管理线程的生命周期,线程同步。线程同步对数据加锁会有一定系统开销。NSThread实现的技术有三种 * Cocoa threads* POSIX threads* Multiprocessing Services## 创建方式
(id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument //实例方法
(void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument //类方法
//直接创建线程并且运行
1、[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
//先创建线程对象,再运行线程操作,运行前可以设置线程优先级等线程信息。
2、NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(doSomething:)
object:nil];
[myThread start];
//不显式创建线程的方法,使用NSObject的类方法创建一个线程
3、[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
#import “ViewController.h”
#define kURL @”http://avatar.csdn.net/2/C/D/1_totogo2010.jpg “
@interface ViewController ()
@end
@implementation ViewController
-(void)downloadImage:(NSString *) url{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *image = [[UIImage alloc]initWithData:data];
if(image == nil){
}else{
//更新主线程外的数据使用performSelector:onThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
}
}
-(void)updateUI:(UIImage*) image{
self.imageView.image = image;
}
{
[super viewDidLoad];
// [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL];
[thread start];
}
@end
1 2 3 4 5 ## NSThread的线程同步,使用NSLock 卖票的例子
#import <UIKit/UIKit.h>
@class ViewController;
@interface AppDelegate : UIResponder
{
int tickets;
int count;
NSThread* ticketsThreadone;
NSThread* ticketsThreadtwo;
NSCondition* ticketsCondition;
NSLock *theLock;
}
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) ViewController *viewController;
@end
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
tickets = 100;
count = 0;
theLock = [[NSLock alloc] init];
// 锁对象
ticketsCondition = [[NSCondition alloc] init];
ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[ticketsThreadone setName:@"Thread-1"];
[ticketsThreadone start];
ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[ticketsThreadtwo setName:@"Thread-2"];
[ticketsThreadtwo start];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
(void)run{
while (TRUE) {
// 上锁
// [ticketsCondition lock];
[theLock lock];
if(tickets >= 0){
[NSThread sleepForTimeInterval:0.09];
count = 100 - tickets;
NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
// [ticketsCondition unlock];
}
}
1 2 3 4 5 6 7 没有线程同步lock,卖票数可能会是-1. 加上能够保证数据的正确。 ## NSThread线程的顺序执行同步 可以通过[NSCondition signal]的方式发送信号,在一个线程唤醒另一个线程等待
#import “AppDelegate.h”
#import “ViewController.h”
@implementation AppDelegate
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
tickets = 100;
count = 0;
theLock = [[NSLock alloc] init];
// 锁对象
ticketsCondition = [[NSCondition alloc] init];
ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[ticketsThreadone setName:@"Thread-1"];
[ticketsThreadone start];
ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[ticketsThreadtwo setName:@"Thread-2"];
[ticketsThreadtwo start];
NSThread *ticketsThreadthree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil];
[ticketsThreadthree setName:@"Thread-3"];
[ticketsThreadthree start];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
-(void)run3{
while (YES) {
[ticketsCondition lock];
[NSThread sleepForTimeInterval:3];
[ticketsCondition signal];
[ticketsCondition unlock];
}
}
(void)run{
while (TRUE) {
// 上锁
[ticketsCondition lock];
[ticketsCondition wait];
[theLock lock];
if(tickets >= 0){
[NSThread sleepForTimeInterval:0.09];
count = 100 - tickets;
NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);
tickets--;
}else{
break;
}
[theLock unlock];
[ticketsCondition unlock];
}
}
1 2 3 4 5 6 7 wait 是等待,假啊一个线程3 去唤醒其它两个线程锁中的wait 可以使用@synchronized 来简化NSLock使用,这样就不必显示编写创建NSLock加锁并解锁相关代码。
(void)doSomeThing:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
1 2 3 4 5 6 7 8 9 10 11 12 还有些其它的锁对象,比如循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等,可查阅官方文档。 # Run Loops * Run loop 比GCD和操作队列要容易,不必处理并发中复杂情况就能异步执行。 * 主线程配置main run loop ,其它线程默认都没有配置run loop 。一般都在主线程中调用后分配给其它队列。如果要在其它线程添加run loop 至少添加一个input source,不然一运行就会退出。 # 在后台操作UI ## 使用操作队列处理
//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];
}];
}];
1 2 3 4 5 ## drawRect在后台绘制 drawRect: 方法会影响性能,所以可以放到后台执行。
//使用UIGraphicsBeginImageContextWithOptions取代UIGraphicsGetCurrentContext:方法
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
// drawing code here
UIImage *i = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return I;
1 2 3 4 5 6 7 8 9 可以把这个方法运用到table view 中,使table view 的cell在滚出边界时能在didEndDisplayingCell委托方法中取消。WWDC中有讲解:Session 211 – Building Concurrent User Interfaces on iOS [https://developer.apple.com/videos/wwdc/2012 /](https://developer.apple.com/videos/wwdc/2012 /) 还有个使用CALayer里drawsAsynchronously属性的方法。不过有时work ,有时不一定。 # 网络异步请求 网络都要使用异步方式,但是不要直接使用dispatch_async,这样没法取消这个网络请求。dataWithContentsOfURL:的超时是30 秒,那么这个线程需要干等到超时完。解决办法就是使用NSURLConnection的异步方法,把所有操作转化成operation来执行。NSURLConnection是通过run loop 来发送事件的。AFNetworking是建立一个独立的线程设置一个非main run loop 。下面是处理URL连接重写自定义operation子类里的start 方法
{
NSURLRequest* request = [NSURLRequest requestWithURL:self.url];
self.isExecuting = YES;
self.isFinished = NO;
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
self.connection = [NSURLConnectionconnectionWithRequest:request
delegate:self];
}];
}
1 2 3 重写start 方法需要管理isExecuting和isFinished状态。下面是取消操作的方法
{
[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;
}
1 2 3 4 5 异步处理文件可以使用NSInputStream。官方文档:[http:// developer.apple.com/library/i os/#documentation/ FileManagement/Conceptual/ FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/ TechniquesforReadingandWritingCustomFiles.html](http:// developer.apple.com/library/i os/#documentation/ FileManagement/Conceptual/ FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/ TechniquesforReadingandWritingCustomFiles.html)实例:[https://gi thub.com/objcio/i ssue-2 -background-file-io](https://gi thub.com/objcio/i ssue-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;
}
}];
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # 并发开发会遇到的困难问题 ## 多个线程访问共享资源 比如两个线程都会把计算结果写到一个整型数中。为了防止,需要一种互斥机制来访问共享资源 ## 互斥锁 同一时刻只能有一个线程访问某个资源。某线程要访问某个共享资源先获得共享资源的互斥锁,完成操作再释放这个互斥锁,然后其它线程就能访问这个共享资源。 还有需要解决无序执行问题,这时就需要引入内存屏障。 在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 preference[http://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock](http://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock),或read-copy-update算法[http://en.wikipedia.org/wiki/Read-copy-update](http://en.wikipedia.org/wiki/Read-copy-update)
## 优先级反转
运行时低优先级任务由于先取得了释放了锁的共享资源而阻塞了高优先级任务,这种情况叫做优先级反转
## 最佳安全实践避免问题的方法
从主线程中取到数据,利用一个操作队列在后台处理数据,完后返回后台队列中得到的数据到主队列中。这样的操作不会有任何锁操作。
## 并发测试
* 使用SenTestingKit框架测试:[https://github.com/nxtbgthng/SenTestingKitAsync](https://github.com/nxtbgthng/SenTestingKitAsync)
* kiwi:[https://github.com/allending/Kiwi](https://github.com/allending/Kiwi)
* GHunit:[https://github.com/gabriel/gh-unit/](https://github.com/gabriel/gh-unit/)