您的位置:首页 > 其它

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等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。

示例: 后台加载显示图片:

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系统版本新特性
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: