GCD中的一些高级特性
2014-05-06 09:18
375 查看
在开发中执行比较耗时的程序的时候,我们通常都是运用多线程的方式来处理的,在iOS的开发中我们可以使用GCD来处理,一旦用到了多线程就不可避免的会出现线程之间的锁的问题(死锁,读写锁,互斥锁等等)。下面我们就来谈谈在GCD中如何避免多线程操作的读写锁以前其他的一些特性。
假设,我们有一个
通过上述的代码就能够很好的处理多线程对dataArray数组的操作,并且不会出现读写锁的问题。
如果当前有3张图片,我们需要从网络上面下载,然后全部下载完成之后,我们需要统一的提醒用户下载完成,这个时候因为每一个图片下载都是并发的,传统的方法是每一个线程各自都有一个bool变量,然后我们去魂环判断这么变量是不是都被置成已完成的状态。现在我们来运用GCD中的group特性来统一处理:
1.运用GCD避免读写锁导致线程安全问题
假设,我们有一个DataManager类,它用来管理整个程序中的数据,再操作的时候就会出现添加数据和读取数据的操作,如果在同一个时间点,两个线程同时要对数据进行添加操作的话,则就会发生操作错误的问题,为了解决这个我们运用到GCD中的barrier和sync操作:
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 | @interface DataManager () @property (strong, nonatomic) NSMutableArray *dataArray; @property (strong, nonatomic) dispatch_queue_t concurrentDataQueue; @end @implementation DataManager + (instancetype)shareInstance { static DataManager *dataManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ dataManager = [[DataManager alloc] init]; dataManager.concurrentDataQueue = dispatch_queue_create("com.kingiol.gcd.dataManagerQueue", DISPATCH_QUEUE_CONCURRENT); }); return dataManager; } - (NSArray *)data { __block NSArray *returnData = nil; dispatch_sync(self.concurrentDataQueue, ^{ returnData = [_dataArray copy]; }); return returnData; } - (void)addData:(id)data { if (data) { dispatch_barrier_async(self.concurrentDataQueue, ^{ [_dataArray addObject:data]; }); } } @end |
2.运用GCD的group来监听一组并发是否运行结束
如果当前有3张图片,我们需要从网络上面下载,然后全部下载完成之后,我们需要统一的提醒用户下载完成,这个时候因为每一个图片下载都是并发的,传统的方法是每一个线程各自都有一个bool变量,然后我们去魂环判断这么变量是不是都被置成已完成的状态。现在我们来运用GCD中的group特性来统一处理:1 2 3 4 5 6 7 8 9 10 11 12 13 | dispatch_group_t downloadGroup = dispatch_group_create(); for (NSInteger i = 0; i < 3; i++) { dispatch_group_enter(downloadGroup); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //下载图片程序 //在图片下载完成的block中调用: dispatch_group_leave(downloadGroup); }); } dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"全部下载完成" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil]; [alertView show]; });随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势“”阅读器并发编程之GCD在《并发编程之Operation Queue》中讲了Cocoa并发编程中的Operation Queue,了解了Operation Queue是一个面向对象的并发编程接口,它支持并发数,线程优先级,任务优先级,任务依赖关系等多种配置,可以方便满足各种复杂的多任务处理场景。本篇将接着讲另一种并发编程机制 – GCD(Grand Central Dispatch)。iOS4.0中首度引入GCD,GCD是管理任务执行的一项技术,它使得我们对多任务处理变得更加方便和有效。它支持同步或异步任务处理,串行或并行的处理队列(Dispath Queue),非系统调用的信号量机制,定时任务处理,进程、文件或网络的监听任务等。这个庞大的任务处理技术大大减少了线程的管理工作,使基于任务的开发变得更加高效。 Dispatch QueueDispatch Queue是一个任务执行队列,可以让你异步或同步地执行多个Block或函数。Dispatch Queue是FIFO的,即先入队的任务总会先执行。目前有三种类型的Dispath Queue:1.串行队列(Serial dispatch queue)2.并发队列(Concurrent dispatch queue)3.主队列(Main dispatch queue) 串行队列串行队列一次只能处理一个任务,可以由用户调用dispatch_queue_create创建:dispatch_queue_t queue; queue = dispatch_queue_create("com.example.MyQueue", NULL); dispatch_queue_create第一个参数是串行队列标识,一般用反转域名的格式表示以防冲突;第二个参数是queue的类型,设为NULL时默认是DISPATCH_QUEUE_SERIAL,将创建串行队列,在必要情况下,你可以将其设置为DISPATCH_QUEUE_CONCURRENT来创建自定义并行队列。 并行队列并行队列可以同时处理多个任务,在不得以的情况下可以用dispatch_queue_create创建,但一般我们都要用系统预定义的并行队列,即全局队列(Global Concurrent Dispatch Queues)。目前系统预定义了四个不同运行优先级的全局队列,我们可以通过dispatch_get_global_queue来获取它们。dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_get_global_queue第一个参数是队列的优先级,分别对应四个全局队列:DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND dispatch_get_global_queue中第二个参数目前系统保留,请设置为0即可。 主队列主队列是一个特殊的队列,它是系统预定义的运行在主线程的一个Dispatch Queue。可以通过dispatch_get_main_queue来获取唯一的主队列。主队列一般运行一些需要与主线程同步的一些短时任务。dispatch_queue_t mainQueue = dispatch_get_main_queue(); 获取当前队列你可以通过dispatch_get_current_queue获取运行时的队列:dispatch_queue_t currentQueue = dispatch_get_current_queue(); 如果在队列执行任务中调用,返回执行此任务的队列;如果在主线程中调用,将返回主队列;如果在一般线程(非主线程线程非队列执行任务)中调用,返回DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列。 在队列中运行任务你可以随时向一个队列中添加一个新任务,只需要调用一下dispatch_async即可:dispatch_async(aQueue, ^{ //Do some work; }); dispatch_async中的任务是异步执行的,就是说dispatch_async添加任务到执行队列后会立刻返回,而不会等待任务执行完成。然而,必要的话,你也可以调用dispatch_sync来同步的执行一个任务:dispatch_sync(aQueue, ^{ //Do some work; }); dispatch_sync会阻塞当前线程直到提交的任务完全执行完毕。 Dispatch Queue的内存管理除了系统预定义的Dispatch Queue,我们自定义的Dispatch Queue需要手动的管理它的内存。dispatch_retain和dispatch_release这两个函数可以控制Dispatch Queue的引用计数(同时可以控制后面会讲到的Dispatch Group和Dispatch Source的引用计数)。当Dispatch Queue引用计数变为0后,就会调用finalizer,finalizer是Dispatch Queue销毁前调用的函数,用来清理Dispatch Queue的相关资源。可以用dispatch_set_finalizer_f函数来设置Dispatch Queue的finalizer,这个函数同时可以设置Dispatch Group和Dispatch Source的销毁函数(后面会讲到)。void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer); Dispatch Queue的上下文环境数据我们可以为每个Dispatch Queue设置一个自定义的上下文环境数据,调用dispatch_set_context来实现。同时我们也可以用dispatch_get_context获取这个上下文环境数据,这个函数同时可以设置Dispatch Group和Dispatch Source的上下文环境数据(后面会讲到)。void dispatch_set_context(dispatch_object_t object,void *context); void * dispatch_get_context(dispatch_object_t object); 注意Dispatch Queue并不保证这个context不会释放,不会对它进行内存管理控制。我们需要自行管理context的内存分配和释放。一般我们非配内存设置context后,可以在finalizer里释放context占有的内存。 并行执行循环在编程过程中,我们经常会用到for循环,而且for循环要做很多相关的任务。比如:for (i = 0; i < count; i++) { //do a lot of work here. doSomething(i); } 如果for循环中处理的任务是可并发的,显然放到一个线程中处理是很慢的,GCD提供两个函数dispatch_apply和dispatch_apply_f,dispatch_apply是用于Block的,而dispatch_apply_f可以用于c函数,它们可以替代可并发的for循环,来并行的运行而提高执行效率。dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(count, queue, ^(size_t i) { //do a lot of work here. doSomething(i); }); Dispatch Group有时候我们进行下一步操作,而这个操作需要等待几个任务处理完毕后才能继续,这时我们就需要用的Dispatch Group(类似thread join)。我们可以把若干个任务放到一个Dispatch Group中:dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ // Some asynchronous work }); dispatch_group_async跟dispatch_async一样,会把任务放到queue中执行,不过它比dispatch_async多做了一步操作就是把这个任务和group相关联。 把一些任务放到Dispatch Group后,我们就可以调用dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。注意:返回后group会清空。dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // Do some work after. dispatch_release(group); Dispatch信号量很多程序设计都设计到信号量,生产者-消费者模型在多线程编程中会频繁的使用。GCD提供了自己的一套信号量机制。dispatch_semaphore_t sema = dispatch_semaphore_create(RESOURCE_SIZE); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); //do some work here. dispatch_semaphore_signal(sema); dispatch_semaphore_wait用来获取信号量,若信号量为0,则等待直到信号量大于0。在处理任务结束后,应释放相关资源并调用dispatch_semaphore_signal使信号量增加1个。 Dispatch SourceDispatch Source是GCD中监听一些系统事件的有个Dispatch对象,它包括定时器、文件监听、进程监听、Mach port监听等类型。 可以通过dispatch_source_create创建一个Dispatch Source:dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue); 这里可以指定Dispatch Source的类型,type可以为文件读或写、进程监听等。handle为监听对象的句柄,如果是文件就是文件描述符,如果是进程就是进程ID。mask用来指定一些想要监听的事件,它的意义取决于type。queue指定事件处理的任务队列。 创建好Dispatch Source后,我们要为Dispatch Source设置一个事件处理模块。可以用dispatch_source_set_event_handler或dispatch_source_set_event_handler_f来设置:void dispatch_source_set_event_handler( dispatch_source_t source, dispatch_block_t handler); 设置好Dispatch Source后就可以调用dispatch_resume来启动监听。如果相应的事件发生就会触发事件处理模块。 同时我们也可以设置一个取消处理模块:dispatch_source_set_cancel_handler(mySource, ^{ close(fd); // Close a file descriptor opened earlier. }); 取消处理模块会在Dispatch Source取消时调用。 下面介绍一下主要的Dispatch Source类型和示例代码。 定时器定时器Dispatch Source可以每隔一个固定的时间处理一下任务。dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); if (timer) { dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); dispatch_source_set_event_handler(timer, block); dispatch_resume(timer); } return timer; } void MyCreateTimer() { dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC, dispatch_get_main_queue(), ^{ MyPeriodicTask(); }); // Store it somewhere for later use. if (aTimer) { MyStoreTimer(aTimer); } } dispatch_after和dispatch_after_f有时候我们只想处理一次延迟任务,可以用dispatch_after和dispatch_after_fvoid dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); 监听文件事件监听文件事件分好几个类型,有读、写、属性的监听。 读取文件dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue); dispatch_source_set_event_handler(source, ^{ // Get some data from the source variable, which is captured // from the parent context. size_t estimated = dispatch_source_get_data(source); // Continue reading the descriptor... }); dispatch_resume(source); 写文件dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue); if (!writeSource) { close(fd); return NULL; } dispatch_source_set_event_handler(writeSource, ^{ size_t bufferSize = MyGetDataSize(); void* buffer = malloc(bufferSize); size_t actual = MyGetData(buffer, bufferSize); write(fd, buffer, actual); free(buffer); // Cancel and release the dispatch source when done. dispatch_source_cancel(writeSource); }); 监听文件属性dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_RENAME, queue); if (source) { // Copy the filename for later use. int length = strlen(filename); char* newString = (char*)malloc(length + 1); newString = strcpy(newString, filename); dispatch_set_context(source, newString); // Install the event handler to process the name change dispatch_source_set_event_handler(source, ^{ const char* oldFilename = (char*)dispatch_get_context(source); MyUpdateFileName(oldFilename, fd); }); // Install a cancellation handler to free the descriptor // and the stored string. dispatch_source_set_cancel_handler(source, ^{ char* fileStr = (char*)dispatch_get_context(source); free(fileStr); close(fd); }); // Start processing events. dispatch_resume(source); } else close(fd); 监听进程事件dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, parentPID, DISPATCH_PROC_EXIT, queue); if (source) { dispatch_source_set_event_handler(source, ^{ MySetAppExitFlag(); dispatch_source_cancel(source); dispatch_release(source); }); dispatch_resume(source); } 监听中断信号dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue); if (source) { dispatch_source_set_event_handler(source, ^{ MyProcessSIGHUP(); }); // Start processing signals dispatch_resume(source); } 参考文献Dispatch Queues:https://developer.apple.com/library/mac/documentation/general/conceptual/concurrencyprogrammingguide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1Dispatch Sources:https://developer.apple.com/library/mac/documentation/general/conceptual/concurrencyprogrammingguide/GCDWorkQueues/GCDWorkQueues.html |
相关文章推荐
- Java的一些高级特性(三)——关于反射
- python-网络爬虫初学二:headers的设置和一些高级特性
- Hibernate一些高级特性
- Java的一些高级特性(七)——Java7中的I/O
- Object-C高级编程读书笔记(6)—— GCD的一些函数
- Java的一些高级特性(六)——Java7中的目录和文件管理
- Python 的一些高级特性
- 部分JDK1.5新特性,一些类(高级for,可变参数,静态导入,System,Runtime,Date,Math)
- 实例简介PHP的一些高级面相对象编程的特性
- 工具类的一些方法及高级特性
- 实例简介PHP的一些高级面向对象编程的特性
- Spring IOC的一些高级特性
- Java的一些高级特性(五)——测试和异常处理
- Java的一些高级特性(八)——Java7中的线程
- 10.2 NSOperation/NSOperationQueue:提供了一些在GCD中不容易实现的特性,如:限制最大并发数量,操作之间的依赖关系.
- 深入理解Java虚拟机(JVM高级特性与最佳实践java虚拟机)的一些知识总结
- Java的一些高级特性(三)——类部分
- python-网络爬虫初学二:headers的设置和一些高级特性
- Java的一些高级特性(一)——Java 7新特性
- CSS3.0的一些高级新特性