GCD(Grand Center Dispatch)使用详解
2016-02-23 23:12
225 查看
一、GCD简介
GCD是苹果公司推出的基于多核CPU多线程技术,GCD属于系统级的线程管理,在Dispatch queue中执行需要执行的任务性能非常高。GCD这块已经开源,地址是 http://libdispatch.macosforge.org。
二、按获取方式分类队列
dispatch queue队列就是执行任务的队列,由于遵循 First in First out(先进先出),所以又称为 FIFO队列。队列有一个基本概念是串行队列和并发队列。
GCD中按获取方式划分:
1. 系统公开队列
系统公开队列是直接可以获取,不需要创建,而且开发者也不能够创建该队列。
系统公开队列又分为5种:
主队列、四种通用调度队列:
四种通用调度队列:
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.1 运行到主线程上的main queue
1.2 High Priority Queue
1.3 Default Priority Queue
1.4 Low Priority Queue
1.5 Background Priority Queue(用于I / O)
可以使用下面的方法简化QoS等级参数的写法:
2 . 自定义队列。
自定义队列有两种类型:串行队列、并发队列
串行队列:底层维护了一个线程池,线程池中维护了一个线程,提交任务到该类队列上时,只能提交到一个线程上,不能实现多线程编程;
并发队列:底层维护了一个线程池,线程池中维护了多个线程,提交任务到该类队列上时,任务由系统自动配置多个线程上,能实现多线程编程。
五、自定义队列优先级设置方式
自定义队列优先级:可以通过dispatch_queue_attr_make_with_qos_class或者dispatch_set_target_queue方法设置队列优先级
七、各种队列使用时机
主队列(顺序):队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。
八、dispatch_groups用法
dispatch groups 是专门用来监视多个异步任务,dispatch_group_t实例用来追踪不同队列中的不同任务。
当group里所有事件都执行完成, GCD API 有两种方式发送通知。第一种是dispatch_group_wait,这种方式会阻塞当前线程,等所有任务都完成或者等待超时;第二种方式是使用dispatch_group_notify,异步执行闭包,不会阻塞当前线程。
第一种使用dispatch_group_wait的Swift例子:
Objective-C例子:
第二种使用dispatch_group_notify的Swift例子:
Objective-C例子:
对现有API 扩展dispatch_group_t :
Note:
dispatch_group_enter与dispatch_group_leave的组合等价于dispatch_group_async;
dispatch_group_enter与dispatch_group_leave必须成对出现。
九、dispatch_after用法
dispatch_after 只是延后提交block,并不是延时执行
十、dispatch_barrier_async 用法
Dispatch_barrier_async确保提交的闭包是指定队列中特定时段唯一在执行的一个。在所有先于Dispatch_barrier_async提交的任务都完成的情况下这个闭包才会执行。轮到这个闭包执行的时候,该队列其他任务将不会被执行,执行完之后,队列恢复。需要注意的是Dispatch_Barrier_async只能用到自己创建的并发队列上,全局并发队列和串行队列并不起作用,效果和dispatch_sync一样。
都用异步处理避免死锁,异步的缺点在于调试不方便,但是比起同步容易产生死锁这个副作用还算小的。
十一、dispatch_once用法
dispatch_once_t要是全局或static变量,保证dispatch_once_t只有一份实例
十二、dispatch_async用法
设计一个异步的API,需要条用dispatch_async(),这个调用放在API的方法或函数中做。让使用者设置一个回调的处理队列
执行一些比较耗时的操作,且易卡住主线程的,比如读取网络数据,大数据IO,还有大量数据数据库读写。这是需要另起线程,然后通知主线程更新界面。GCD使用起来比NSOperation与NSThread方便简单许多。
十三、dispatch_apply用法
类似for循环,但是在并发队列的情况下,并发执行block
dispatch_apply能避免线程爆炸,因为GCD会管理并发
十四、dispatch_block_t用法
十五、dispatch_block_t在任务执行前进行取消
十六、dispatch_io_t用法
十七、dispatch_source用法
十八、dispatch_semaphore用法
十九、dispatch_suspend和dispatch_resume用法
二十、dispatch_set_context和dispatch_get_context
二十一、GCD死锁
二十二、GCD实际使用
二十三iOS系统版本新特性
GCD是苹果公司推出的基于多核CPU多线程技术,GCD属于系统级的线程管理,在Dispatch queue中执行需要执行的任务性能非常高。GCD这块已经开源,地址是 http://libdispatch.macosforge.org。
二、按获取方式分类队列
dispatch queue队列就是执行任务的队列,由于遵循 First in First out(先进先出),所以又称为 FIFO队列。队列有一个基本概念是串行队列和并发队列。
GCD中按获取方式划分:
1. 系统公开队列
系统公开队列是直接可以获取,不需要创建,而且开发者也不能够创建该队列。
系统公开队列又分为5种:
主队列、四种通用调度队列:
四种通用调度队列:
QOS_CLASS_USER_INTERACTIVE: user interactive等级表示任务需要被立即执行提供好的体验,用来更新UI,响应事件等。这个等级最好保持小规模。
QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务需要节能。
QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
示例: 后台加载显示图片:
func loadRemoteImage(URLString: String){ dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) { let image = self.downloadImageFromURL(URLString) dispatch_async(dispatch_get_main_queue()) { // 图片完成,把一个闭包提交到主线程上更新UI self.easeInNewImage(image) // 更新 UI } } }
1.1 运行到主线程上的main queue
dispatch_get_main_queue()
1.2 High Priority Queue
dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)
1.3 Default Priority Queue
dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)
1.4 Low Priority Queue
dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.rawValue), 0)
1.5 Background Priority Queue(用于I / O)
dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.rawValue), 0)
可以使用下面的方法简化QoS等级参数的写法:
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) } }
2 . 自定义队列。
自定义队列有两种类型:串行队列、并发队列
串行队列:底层维护了一个线程池,线程池中维护了一个线程,提交任务到该类队列上时,只能提交到一个线程上,不能实现多线程编程;
dispatch_queue_create("com.manchelle.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL)
并发队列:底层维护了一个线程池,线程池中维护了多个线程,提交任务到该类队列上时,任务由系统自动配置多个线程上,能实现多线程编程。
dispatch_queue_create("com.manchelle.gcddemo.concurrent", DISPATCH_QUEUE_CONCURRENT)
五、自定义队列优先级设置方式
自定义队列优先级:可以通过dispatch_queue_attr_make_with_qos_class或者dispatch_set_target_queue方法设置队列优先级
/** dipatch_queue_attr_make_with_qos_class 方式 * QOS_CLASS_USER_INITIATED * * QOS_CLASS_USER_INTERACTIVE * * QOS_CLASS_UTILITY * * QOS_CLASS_BACKGROUND */ dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1); dispatch_queue_t queue = dispatch_queue_create("com.manchelle.gcddemo.qosqueue", attr);
/** dispatch_set_target_queue * * * */ dispatch_queue_t queue = dispatch_queue_create("com.manchelle.gcddemo.settargetqueue",DISPATCH_QUEUE_CONCURRENT); //需要设置优先级的queue dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级 dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样
七、各种队列使用时机
主队列(顺序):队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。
八、dispatch_groups用法
dispatch groups 是专门用来监视多个异步任务,dispatch_group_t实例用来追踪不同队列中的不同任务。
当group里所有事件都执行完成, GCD API 有两种方式发送通知。第一种是dispatch_group_wait,这种方式会阻塞当前线程,等所有任务都完成或者等待超时;第二种方式是使用dispatch_group_notify,异步执行闭包,不会阻塞当前线程。
第一种使用dispatch_group_wait的Swift例子:
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) } } } }
Objective-C例子:
- (void)dispatchGroupWaitDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); //在group中添加队列的block dispatch_group_async(group, concurrentQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog(@"1"); }); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"2"); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"go on"); }
第二种使用dispatch_group_notify的Swift例子:
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) } } }
Objective-C例子:
//dispatch_group_notify - (void)dispatchGroupNotifyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"1"); }); dispatch_group_async(group, concurrentQueue, ^{ NSLog(@"2"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"end"); }); NSLog(@"can continue"); }
对现有API 扩展dispatch_group_t :
// NSURLSession 例子 extension NSURLSession { public func withGroup(group: dispatch_group_t?, request: NSURLRequest, completionHandler:(NSData?, NSURLResponse?, NSError?) -> Void) { if group == nil { self.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in completionHandler(data,response,error) }) }else { dispatch_group_enter(group!) self.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in completionHandler(data,response,error) dispatch_group_leave(group!) }) } } }
//给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); }]; } }
Note:
dispatch_group_enter与dispatch_group_leave的组合等价于dispatch_group_async;
dispatch_group_enter与dispatch_group_leave必须成对出现。
九、dispatch_after用法
dispatch_after 只是延后提交block,并不是延时执行
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))) dispatch_after(time, GlobalMainQueue, { () -> Void in })
// 解释一下上面参数 dispatch_time(argv1,argv2) 第一个参数: DISPATCH_TIME_NOW 描述从现在开始计时; 第二个参数: 延时时间,单位为纳秒,所以秒数要乘以一秒的毫秒数; 系统提供了几个宏定义: NSEC_PER_MSEC: 一毫秒多少纳秒 NSEC_PER_SEC: 一秒多少纳秒 NSEC_PER_USEC: 一微秒多少纳秒
十、dispatch_barrier_async 用法
Dispatch_barrier_async确保提交的闭包是指定队列中特定时段唯一在执行的一个。在所有先于Dispatch_barrier_async提交的任务都完成的情况下这个闭包才会执行。轮到这个闭包执行的时候,该队列其他任务将不会被执行,执行完之后,队列恢复。需要注意的是Dispatch_Barrier_async只能用到自己创建的并发队列上,全局并发队列和串行队列并不起作用,效果和dispatch_sync一样。
/** * dispatch_Barrier_async 简单模拟读写操作结构 * */ func dispatchBarrierAsyncDemo(){ // 为防止读写冲突,可以创建一个并发队列,没有数据更新的只读操作可以异步执行,而写操作时必须保证其他操作不能同时进行。 let dataQueue = dispatch_queue_create("com.manchelle.gcddemo.dispatch_barrier", DISPATCH_QUEUE_CONCURRENT) dispatch_async(dataQueue) { print("read data!") NSThread.sleepForTimeInterval(1.0) } dispatch_async(dataQueue) { print("read data!") NSThread.sleepForTimeInterval(2.0) } // 等前面的任务执行完成之后才能单独执行barrier中的任务,而且barrier后面的也不能执行。 dispatch_barrier_async(dataQueue) { print("write data!") NSThread.sleepForTimeInterval(3.0) } dispatch_async(dataQueue) { print("read data!") NSThread.sleepForTimeInterval(4.0) } dispatch_async(dataQueue) { print("read data!") NSThread.sleepForTimeInterval(5.0) } }
// 解决读写操作的代码框架 var _newsArray:[New] = [] private let concurrentQueue = dispatch_queue_create("com.manchelle.gcddemo.dispatch_barrier", DISPATCH_QUEUE_CONCURRENT) // 解决写死锁 func addNew(new: New) { dispatch_barrier_async(concurrentQueue) { // 写操作,为防止多个线程同时写操作造成死锁,使用barrier在同一时刻只允许这一个任务执行。 self._newsArray.append(new) dispatch_async(dispatch_get_main_queue()) { // 更新UI } } } // 上面解决了写操作可能发生死锁,下面使用dispatch_sync解决读时可能发生死锁 var newsArray:[New] { var newsCopy:[New]! dispatch_sync(concurrentQueue) { newsCopy = self._newsArray } return newsCopy }
都用异步处理避免死锁,异步的缺点在于调试不方便,但是比起同步容易产生死锁这个副作用还算小的。
十一、dispatch_once用法
dispatch_once_t要是全局或static变量,保证dispatch_once_t只有一份实例
+ (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_async用法
设计一个异步的API,需要条用dispatch_async(),这个调用放在API的方法或函数中做。让使用者设置一个回调的处理队列
- (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); }); }); }
执行一些比较耗时的操作,且易卡住主线程的,比如读取网络数据,大数据IO,还有大量数据数据库读写。这是需要另起线程,然后通知主线程更新界面。GCD使用起来比NSOperation与NSThread方便简单许多。
// 代码框架 func frameDemo() { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){ // 耗时的操作 dispatch_async(dispatch_get_main_queue(), { () -> Void in // 更新UI }) } } // 下载图片示例 func downloadImageDemo() { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){ let URL = NSURL(string: "http://avatar.csdn.net......") let data: NSData? = NSData(contentsOfURL: URL!) dispatch_async(dispatch_get_main_queue(), { () -> Void in if let tempData = data { self.imageView.image = UIImage(data: tempData) } }) } }
十三、dispatch_apply用法
类似for循环,但是在并发队列的情况下,并发执行block
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可以运行更快 let concurrentQueue = dispatch_queue_create("com.manchelle", DISPATCH_QUEUE_CONCURRENT) dispatch_apply(10, concurrentQueue) { (i) -> Void in print("\(i)") } print("apply end") // dispatch_apply会阻塞线程,所以要等上诉block执行完成之后才会执行这句
dispatch_apply能避免线程爆炸,因为GCD会管理并发
func dealWithThreadWithMaybeExplode(explode: Bool) { if(explode) { //有问题的情况,可能会死锁 for (var i = 0; i < 999 ; i++) { dispatch_async(dispatch_queue_create("com.manchell.gcddemo", DISPATCH_QUEUE_CONCURRENT)) { print("wrong == \(i)") } } } else { //会优化很多,能够利用GCD管理 dispatch_apply(999, dispatch_queue_create("com.manchell.gcddemo", DISPATCH_QUEUE_CONCURRENT), { (n) -> Void in print("wrong == \(n)") }) } }
// 示例: 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) } } }
十四、dispatch_block_t用法
十五、dispatch_block_t在任务执行前进行取消
十六、dispatch_io_t用法
十七、dispatch_source用法
十八、dispatch_semaphore用法
十九、dispatch_suspend和dispatch_resume用法
二十、dispatch_set_context和dispatch_get_context
二十一、GCD死锁
二十二、GCD实际使用
二十三iOS系统版本新特性
相关文章推荐
- 坑爹的工行一代U盾
- php 通过http user-agent判断是否为手机浏览器
- 传真知识总结
- Mybatis的<where><foreach><set>等标签详解
- PAT-1010 一元多项式求导
- eval
- JAVA JVM 原理
- 更好的设计数据库还有就是数据库语句优化~~(主要是mysql)
- LeetCode 111. Minimum Depth of Binary Tree
- .NET项目框架(转)
- JAVA JVM 原理
- centos 5.6 简单搭建samba服务器
- mysql优化总结
- Nagios各组件简述及nrpe详解
- kotlin 练习
- Hive 8、Hive2 beeline 和 Hive jdbc
- Yii2.0数据库操作(1)
- 在android中读取word、excel、pdf
- andriod GridLayout
- 第一章 Oracle10g数据库新特性