GCD是苹果公司为多核的并行运算解决方案,会自动利用更多的CPU内核,GCD自动管理线程的生命周期(创建、调度、销毁)
GCD的两个核心概念
- 任务:就是执行操作,具体的说是在线程中执行放在block中的那段代码,执行任务有两种方式:同步和异步,两者的区别:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
同步执行(sync):
- 同步添加任务到指定队列中,中任务执行结束之前,会一直等待,直到队列里的任务完成后再执行。
- 只能在当前线程中执行任务,不具备开启新线程能力。
异步执行(async):
- 异步添加任务到指定的队列中,不会做任务等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程能力。
-
队列:用来存放任务的一种逻辑线性表,采用FIFO原则。GCD中的队列两种:串行队列和并发队列,主要区别:执行顺序不同,开启线程数不同。
串行队列(Serial):
每次只有一个任务被执行,只开启一个线程,一个任务执行完毕后,再执行下一个任务。
并发队列(Concurrent):
- 可以让多个任务并发执行,可以开启多个线程,并且同时执行多任务。
- 注意:并发队列的并发功能只有在异步dispatch_async方法下才有效。
主队列与全局队列:- 主队列中的任务都在主线程中执行,主队列的特点:如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到主线程空闲为止。
- 全局队列本质上就是并发队列,与并发队列区别:并发队列有自定义名称,可以跟踪错误,全局队列没有,在ARC中不需要考虑内存释放,在MRC中需要手动dispatch_release(queue)释放内存,全局队列只有一个由系统管理。
- 一般情况下使用全局队列,但是对于同一类业务创建一个固定的队列进行管理,如SDK为两使被导入的SDK不影响到主应用的其他业务队列操作,建议创建自己专属的队列。
GCD的使用步骤
- 创建一个队列(串行队列或并发队列);
- 将任务追加到指定的队列中,系统会根据任务类型执行任务(同步执行或异步执行)。
使用dispatch_queue_create方法创建队列,两个参数:
- 第一个参数表示队列唯一标识符,可为空,推荐使用应用模块ID逆序域名命名。
- 第二个参数表示是串行队列还是并发队列:DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
- 对于串行队列,默认提供的是『主队列(Main Dispatch Queue)』。
- 再强调一下:,使用dispatch_get_main_queue()获取主队列,主队列实质是个普通的 。
- 对于并发队列, 默认提供了 『全局并发队列(Global Dispatch Queue)』,使用dispatch_get_global_queue方法获取全局并发队列,第一个参数表示队列优先级,一般用DISPATCH_QUEUE_RPIORITY_DEFAULT。第二个参数预置参数,用0即可。
// 串行队列,异步执行:会新建一个线程,按串行顺序执行. DISPATCH_QUEUE_SERIAL = NULL
dispatch_queue_t queue = dispatch_queue_create("com.chshua.dispatch", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%@ --- %d", [NSThread currentThread], i);
});
}
// 并行队列,异步执行:会新建多个线程,无法确定任务执行顺序
dispatch_queue_t concurrent = dispatch_queue_create("com.chshua.dispatch", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<10; i++) {
dispatch_async(concurrent, ^{
NSLog(@"%@ --- %d", [NSThread currentThread], i);
});
}
『主线程』中,『不同队列』+『不同任务』简单组合的区别:
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 死锁卡住不执行 |
异步(async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程, 串行执行任务 |
注意: 『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。
这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题。
而如果我们在 『其他线程』 调用 『主队列』+『同步执行』,则不会阻塞 『主队列』,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务。
主队列
- 主队列,异步任务
- 不开线程,同步执行
- 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后执行任务
- 主队列又叫全局串行队列
- 主队列,同步任务
- 程序执行不出来(死锁)
- 死锁原因:主队列:如果主线程正在执行代码,就不调度任务。
同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)
//主队列的特点:主队列只有当主线程空闲下来的时候才能够执行主队列里面的任务
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"哈哈, 大家都在等吧...");
});
一次执行dispatch_once
作用是保证block在程序生命周期范围内只执行一次,最常见的场景是单例
- 单例模式
+ (instancetype)sharedManager {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
延迟执行dispatch_after
- dispatch_after延迟执行GCD block方法,跟阻塞主线程有区别
- dispatch_after能让添加队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue
/**
* dispatch_time 延迟时间
* dispatch_get_main_queue 延迟执行的队列
**/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_queue_t queue, ^{
// well to be executed after a specified delay
});
多次执行dispatch_apply
- dispatch_apply把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。
/**
* iterations 执行的次数
* queue 提交到的队列
* block 执行的任务
**/
dispatch_apply(size_t iterations, dispatch_queue_t queue, ^{
// code will to be executed
});
阻塞dispatch_barrier
- dispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需要等待它完成之后才执行。 应用场景读写锁操作,为了防止文件读写导致冲突,通常会创建一个串行队列,所有的文件操作都通过该队列来执行。
-
在dispatch_barrier函数时,使用自定义队列才有意义,如果用串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,dispatch_barrier_async 的作用会和 dispatch_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
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 3
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
// 追加任务 4
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
});
}
队列组dispatch_group
- 有这样一个需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
- 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
- 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
/**
* 队列组 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---end");
});
}
- 会暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
/**
* 队列组 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 1
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任务 2
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
从 相关代码运行输出结果可以看出:
当所有任务执行完成之后,才执行 之后的操作。但是,使用 会阻塞当前线程。