GCD使用总结
看了MJ大神的视频与几篇播客深入了解GCD:Part 1/2 深入了解GCD:Part 2/2 GCD使用经验与技巧浅谈之后,自己总结的使用注意
任务
GCD中要执行的操作都可以叫做任务(下载图片、下载文本等)
队列
按照FIFO(先进先出)的顺序帮我们调度任务,GCD会把我们添加到队列中的任务取出,放到线程中执行。队列分为
串行队列
串行队列同一时间只能有一个任务执行,后一个任务只有在前一个任务执行完之后才能被调度到线程中执行
并行队列
并行队列同一时间可以多个任务执行,后一个任务不需要等前一个任务执行完,就可以被调度
任务的提交
任务提交的方式会决定要不要开启新的线程,在GCD中将任务添加到队列中有两种方式
同步提交
将任务同步提交给队列,不会开启新的线程,只会在当前线程执行,并且只有在同步任务执行完之后,才能继续向下执行代码,同步提交的方式:
1 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步提交
任务被异步提交给队列,会开启新的线程,但开多少新的线程,我们不能决定,由系统自己决定,并且不需要等异步任务执行完,就可以继续向下执行代码,异步提交的方式:
1 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
GCD中不同的提交方式与不同队列的组合
串行队列同步提交
1 dispatch_queue_t queue = dispatch_queue_create("com.yys.test", DISPATCH_QUEUE_SERIAL); 2 3 dispatch_sync(queue, ^{ 4 5 // 下载文件,图片等资源 6 7 });首先根据同步提交 -> 没有新的线程开启
然后根据串行队列的特点 -> 同一时间只能执行一个任务
最终得到 -> 不会开启新的线程,任务按照提交顺序在当前线程依次执行
串行队列异步提交
1 dispatch_queue_t queue = dispatch_queue_create("com.yys.test", DISPATCH_QUEUE_SERIAL); 2 3 dispatch_async(queue, ^{ 4 5 // 下载文件,图片等资源 6 7 });根据异步提交 -> 会有新的线程开启
根据串行队列的特点 -> 同一时间只能执行一个任务
最终得到 -> 会开启新的线程,任务在新开启的线程中按照提交顺序依次执行,但是仅仅只会开启一条新的线程,因为异步提交会开启新的线程,但是串行队列只需要一条线程就可以执行所有提交的任务
并行队列同步提交
1 dispatch_queue_t queue = dispatch_queue_create("com.yys.test", DISPATCH_QUEUE_CONCURRENT); 2 3 dispatch_sync(queue, ^{ 4 5 // 下载文件,图片等资源 6 7 });根据同步提交 -> 没有新的线程开启
并行队列 -> 同一时间可以有多个任务被调度,但是在同步提交的条件下,并行队列失去了并行的能力,与串行队列区别不大
最终得到 -> 不会开启新的线程,提交的任务在当前线程按照提交顺序依次执行
并行队列异步提交
1 dispatch_queue_t queue = dispatch_queue_create("com.yys.test", DISPATCH_QUEUE_CONCURRENT); 2 3 dispatch_async(queue, ^{ 4 5 // 下载文件,图片等资源 6 7 });异步提交 -> 可以开启新的线程
并行队列 -> 同一时间可以有多个任务被调度
最终得到 -> 有新的线程开启,可以多个任务同时执行,会开多条线程,但是开多少条线程我们不能控制。所以,可以用来同时下载多张图片
GCD中特别的队列 -> 主队列(串行队列)
主队列是串行队列,它的主要作用就是用来更新UI控件,所有UI控件的刷新都必须在主线程中执行
主队列同步提交(尤其注意)
1 dispatch_queue_t queue = dispatch_get_main_queue(); 2 3 dispatch_sync(queue, ^{ 4 5 // 下载文件,图片等资源 6 7 });主队列同步提交任务一定会发生死锁,就是线程被阻塞,不会再继续向下执行代码
同步提交使用时尤其注意,不能在当前线程再向这个线程中提交任务
主线程在执行 任务A ,在 任务A 中向主队列中添加 任务B ,这时 任务B 会在主线程中执行,由于主队列是串行队列,所以任务会依次执行, 任务A 执行完就会执行 任务B ,但是 任务A 要执行完则 任务B 必须也要执行完,但是 任务B 要等 任务A 执行完才能执行,因此会发生死锁,代码不会向下执行
主队列异步提交
1 dispatch_queue_t queue = dispatch_get_main_queue(); 2 3 dispatch_async(queue, ^{ 4 5 // 下载文件,图片等资源 6 7 });这一情况也是很特殊的,尽管是异步提交,但是没有新的线程开启,GCD会在恰当的时候把你提交的任务在主线程中执行完,执行的时刻不可控
GCD的其他用处
dispatch_after: 延后执行
1 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(需要延后的时间 * NSEC_PER_SEC)); 2 3 dispatch_after(time, dispatch_get_main_queue(), ^{ 4 5 // 需要延后执行的代码 6 7 });需要注意的是,这里并不是在某一个时刻执行任务,只是将任务提交给队列
1 NSLog(@"线程执行开始"); 2 3 dispatch_async(dispatch_get_main_queue(), ^{ 4 5 [NSThread sleepForTimeInterval:10.]; 6 7 }); 8 9 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5. * NSEC_PER_SEC)); 10 11 dispatch_after(time, dispatch_get_main_queue(), ^{ 12 13 NSLog(@"延后提交任务”); 14 15 });执行结果
这里第二次打印是在第一次打印10秒之后,并不是5秒之后
dispatch_once: 只执行一次某段代码
1 static dispatch_once_t onceToken; 2 3 dispatch_once(&onceToken, ^{ 4 5 // 只需要执行一次的代码 6 7 });需要注意的是onceToken一定要用static申明,这样才能保证需要执行一次的代码执行一次,否则的话,不能保证代码只执行一次,会出现难以修复的bug
dispatch_group
使用场景:当你执行多个异步任务,并且要等到所有的任务执行完做某些操作时
1 dispatch_group_t group = dispatch_group_create(); 2 3 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 4 5 // 提交任务A 6 7 }); 8 9 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 10 11 // 提交任务B 12 13 }); 14 15 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 16 17 // 所有任务执行完后,所需要的操作 18 19 });这里有两种方式通知所有的任务完成:
- dispatch_group_notify 这一种是异步通知,不会阻塞当前线程(常用)
- dispatch_group_wait 这一种会一直等待,直到所有的任务完成或者超时
dispatch_barrier_sync和dispatch_barrier_async
1 dispatch_barrier_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 2 3 // 提交任务 4 5 });这两个函数提交的任务,在执行时会阻塞后面的任务,在这一时间内,只有这一任务在执行,后续任务只有在这个任务执行完成后才能执行,并且所有在这个任务之前的任务一定会先于这个任务完成
dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局并发队列、串行队列上,效果跟dispatch_sync、 dispatch_async效果一样