GCD(Grand Center Dispatch)使用详解

来源:互联网 发布:淘宝上可以卖电子书吗 编辑:程序博客网 时间:2024/06/14 22:39

一、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等级表示任务由用户发起异步执行。适用场景是需要及时得到结果同时又可以继续交互的时候。

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); //需要设置优先级的queuedispatch_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系统版本新特性

0 0
原创粉丝点击