GCD因为其强大的处理线程和多核能力,所以在开发使用多线程的时候使用是非常频繁。
一、GCD简介
- 全称:Grand Central Dispatch
- 纯C语言编写
- 可以自动管理更多的CPU内核
- 可以自动管理线程的生命周期(创建、调度、销毁)
- 通过线程池来管理线程,将不用的线程放到线程池中,等待调度使用。
二、GCD中的队列和执行任务的方式
- GCD中队列大致可以分为 :
-
串行队列:Serial Dispatch Queue,每次只有一个任务执行,并且任务是一个接着一个的去执行的。
串行队列 -
并发队列(并行队列):Concurrent Dispatch Queue,可以让任务同时去执行,
并发队列前提条件是在 异步 的情况下
。
-
两种队列同时满足先进先出的原则,两者的主要区别是:执行顺序不同,以及开启线程数不同。
-
一些特殊的队列
- 主队列:dispatch_get_main_queue()
特殊的串行队列,但又不同于串行队列。
以先进先出的形式调度任务,如果主线程上面有任务正在执行,那么会等待主线程任务执行完再去执行任务。 - 全局队列:dispatch_get_global_queue()
全局队列的本质就是并发队列,全局队列一直都存在,所以用 ‘get_’- 全局队列的两个参数:
- 主队列:dispatch_get_main_queue()
* @param identifier
* A quality of service class defined in qos_class_t or a priority defined in
* dispatch_queue_priority_t.
*
* It is recommended to use quality of service class values to identify the
* well-known global concurrent queues:
* - QOS_CLASS_USER_INTERACTIVE
* - QOS_CLASS_USER_INITIATED
* - QOS_CLASS_DEFAULT
* - QOS_CLASS_UTILITY
* - QOS_CLASS_BACKGROUND
*
* The global concurrent queues may still be identified by their priority,
* which map to the following QOS classes:
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
*
* @param flags
* Reserved for future use. Passing any value other than zero may result in
* a NULL return value.
上面是API中的说明:
简单来说:第一个参数是服务质量(A quality of service class)
,也就是 优先级
QOS开头的是被推荐使用的(ios7之后),可以看到和下面的DISPATCH_开头的相对应,优先级从高到低。其中QOS_CLASS_USER_INTERACTIVE的优先级最高。第二个参数是保存起来给将来使用(目前为止都还没有用。。。),通常传个0.
- 执行任务方式:
执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
-
同步执行(sync)
- 只会在当前的线程去执行任务,不具备开启新线程的能力。
- 线程会一个接着一个的去执行。等前一个任务执行完之后,才回去执行后一个任务。
- 当在主线程里,与主队列同时使用的时候,会出现死锁的情况(主队列等在主线程中的任务执行完毕,主线程等待串行队列中的任务执行完毕,
出现相互等待的情况
)
-
异步执行(async):
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
三、GCD的使用
- 使用步骤
GCD使用起来非常的方便:先创建队列,然后将任务追加到等待的队列中,然后系统就自动根据任务的类型执行任务。 - 创建GCD多线程
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("qwer", DISPATCH_QUEUE_SERIAL);
// 创建异步任务
dispatch_async(queue, ^{
NSLog(@"求工作");
});
获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
获取全局队列
dispatch_queue_t gloadQueue = dispatch_get_global_queue(0, 0);
四. 任务和队列的搭配
虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么我们就有了四种不同的组合方式。这四种不同的组合方式是:
同步执行 + 并发队列
异步执行 + 并发队列
同步执行 + 串行队列
异步执行 + 串行队列
实际上,刚才还说了两种特殊队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。但是主队列因为有点特殊,所以我们就又多了四种种组合方式。这样就有八种不同的组合方式了。
同步执行 + 主队列
异步执行 + 主队列
同步执行 + 全局并发队列
异步执行 + 全局并发队列
- 串行队列 + 异步执行 = 开启一条线程,串行执行
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("qwer", DISPATCH_QUEUE_SERIAL);
// 创建任务
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
- 并发队列 + 异步执行 = 开启多条线程,同步执行,执行顺序不可控
dispatch_queue_t queue2 = dispatch_queue_create("qwer", DISPATCH_QUEUE_CONCURRENT);
// 创建任务
for (int i = 0; i < 10; i++) {
dispatch_async(queue2, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
- 主队列 + 异步执行 = 不开启线程,串行执行,在主线程上面执行
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (int i = 0; i < 10; i++) {
dispatch_async(mainQueue, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
- 全局并发队列 + 异步执行 = 开启多条线程,同步执行,执行顺序不可控
dispatch_queue_t gloadQueue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(gloadQueue, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
- 串行队列 + 同步执行 = 不开启线程,任务在主线程上面执行
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("qwer", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
- 并行队列 + 同步执行 = 不开启线程,任务在主线程上面执行
dispatch_queue_t queue2 = dispatch_queue_create("qwer", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_sync(queue2, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
- 主线程 + 同步执行 + 在主线程 = 相互等待,死锁
主线程 + 同步执行 + 非主线程 = 不开启线程,任务在主线程上面执行
// 在主线程下
// 主队列的特点:等待主线程中的代码执行完再去执行主队列中的任务
// 同步执行又是在主线程执行的,并且会等着第一个任务执行完毕
// 出现相互等待
dispatch_queue_t mainQueue = dispatch_get_main_queue();
for (int i = 0; i < 10; i++) {
dispatch_sync(mainQueue, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
// 非主线程
- 全局并发队列 + 同步执行 = 不开启线程,任务在主线程上面执行
dispatch_queue_t gloadQueue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_sync(gloadQueue, ^{
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
通过上面的实际操作可以得出以下的结论:
同步执行任务的时候是不会开启新的线程。异步在主线程的时候不会开启新线程。
例子:在网络下载应用:先输入密码--》开始下载 --> 打开应用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"先输入密码 == %@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"开始下载 == %@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"打开应用 == %@",[NSThread currentThread]);
});
});
四、GCD 栅栏(阻塞)方法:dispatch_barrier_async
当需要异步执行
两组操作,并且第一组执行完毕之后才去执行第二组操作。这样我们就需要一个相当于栅栏
一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async
方法在两个操作组间形成栅栏
。
dispatch_barrier_async
函数会等待
前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
/**
* 栅栏方法 dispatch_barrier_async
*/
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
// 通过栅栏 等上面的线程执行完毕之后再去执行栅栏里面的方法
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_async(queue, ^{
// 追加任务4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
}
});
}
在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。
五、GCD 延迟:dispatch_after
顾名思义:方法在多少秒之后再执行;
需要注意的是:dispatch_after
函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中
。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。
/**
dispatch_time_t when:延迟多长时间执行 精确到纳秒级
dispatch_queue_t queue:队列
dispatch_block_t block:任务
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
六、GCD一次性执行:dispatc_once()
- 一次性执行在当前的线程执行:如果在子线程就在子线程执行,在主线程就在主线程执行。
- 通过判断onceToken的值来实现的,默认onceToken为0,可以执行代码块中的方法,当onceToken不为0的时候,不去执行代码块中的方法。
- 我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。
- 使用dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。
static dispatch_once_t onceToken;
NSLog(@"%zd",onceToken);
dispatch_once(&onceToken, ^{
NSLog(@"%@",[NSThread currentThread]);
});
NSLog(@"%zd",onceToken);
// onceToken先后打印 0 和 -1
//
七、队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
-(void)group{
//创建队列组
dispatch_group_t group = dispatch_group_create() ;
//获得并发队列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT) ;
dispatch_queue_t queue2 = dispatch_queue_create("Test2", DISPATCH_QUEUE_CONCURRENT) ;
//封装任务
//代码实现的步骤 01.封装任务。 02.把任务添加到队列中 03 . 监听任务的执行情况(知道任务什么时候执行)
dispatch_group_async(group, queue, ^{
NSLog(@"0-------%@",[NSThread currentThread]) ;
});
//代码实现的步骤 01.封装任务。 02.把任务添加到队列中
dispatch_group_async(group, queue, ^{
NSLog(@"1-------%@",[NSThread currentThread]) ;
});
dispatch_group_async(group, queue, ^{
NSLog(@"2-------%@",[NSThread currentThread]) ;
});
dispatch_group_async(group, queue2, ^{
NSLog(@"3-------%@",[NSThread currentThread]) ;
});
// 拦截通知,当所有的任务都执行完毕后,执行+++++操作
//这里的queue只是决定block中的任务在那个线程中执行
//dispatch_group_notify。 内部是异步执行
dispatch_group_notify(group, queue, ^{
NSLog(@"++++++%@" ,[NSThread currentThread]) ;
});
}
// 队列组
-(void)group2{
//创建队列组
dispatch_group_t group = dispatch_group_create() ;
//获得并发队列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT) ;
//封装任务
//代码实现的步骤 01.封装任务。 02.把任务添加到队列中 03 . 监听任务的执行情况(知道任务什么时候执行)
dispatch_group_async(group, queue, ^{
NSLog(@"1-------%@",[NSThread currentThread]) ;
});
dispatch_group_async(group, queue, ^{
NSLog(@"2-------%@",[NSThread currentThread]) ;
});
dispatch_group_async(group, queue, ^{
NSLog(@"3-------%@",[NSThread currentThread]) ;
});
dispatch_group_notify(group, queue, ^{
NSLog(@"++++++%@" ,[NSThread currentThread]) ;
});
}
//队列组的实例应用
-(void)group3{
dispatch_group_t group= dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0) ;
//下载图片一
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURLURLWithString:@"[http://e.hiphotos.baidu.com/image/pic/item/adaf2edda3cc7cd9019ce04e3501213fb90e91f0.jpg](http://e.hiphotos.baidu.com/image/pic/item/adaf2edda3cc7cd9019ce04e3501213fb90e91f0.jpg)"] ;
NSData *data = [ NSData dataWithContentsOfURL:url] ;
self.image = [UIImage imageWithData: data] ;
NSLog(@"download-----%@",[NSThread currentThread]) ;
});
//下载图片二
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURLURLWithString:@"[http://h.hiphotos.baidu.com/image/pic/item/50da81cb39dbb6fd35d8b8b30524ab18962b37a7.jpg](http://h.hiphotos.baidu.com/image/pic/item/50da81cb39dbb6fd35d8b8b30524ab18962b37a7.jpg)"] ;
NSData *data = [ NSData dataWithContentsOfURL:url] ;
self.image2 = [UIImage imageWithData: data] ;
NSLog(@"download2-----%@",[NSThread currentThread]) ;
});
//拦截通知,合成图片
dispatch_group_notify(group, queue, ^{
//开启上下文
UIGraphicsBeginImageContext(CGSizeMake(300, 300)) ;
//画图1、2
[self.image drawInRect: CGRectMake(0, 0, 300, 150)] ;
[self.image2 drawInRect:CGRectMake(150, 0, 300, 150)];
NSLog(@"Combie -----%@",[NSThread currentThread]) ;
//根据上下文得到一个图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext() ;
//关闭上下文
UIGraphicsEndPDFContext() ;
//06 显示图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview.image = image;
NSLog(@"UI-------%@",[NSThread currentThread]) ;
});
});
}
参考文章:iOS 多线程:『GCD』详尽总结
求职广告:因上一家公司资金链,全员被迫离职,现在需要一份iOS开发的工作,杭州南京合肥的都可以。对我感兴趣的可以私聊我 0.0。谢谢~~~
再次感谢!
网友评论