简单介绍
什么是GCD?
全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
纯C语言,提供了非常多强大的函数
GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
在使用GCD时,只需要把任务(通常封装在一个Block中)添加到一个队列中执行,只需按一下步骤执行
1.在Block中定义需要执行的任务内容
2.把任务添加到队列queue
3.提示
(1)GCD存在于libdispatch.dylib这个库中,这个调度库包含了GCD的所有的东西,但任何IOS程序,默认就加载了这个库,在程序运行的过程中会动态的加载这个库,不需要我们手动导入。
点击+a按钮,可以导入框架。
(2)GCD是纯C语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法。
(3)GCD中的函数大多数都以dispatch开头。
任务和队列
GCD中有2个核心概念
任务:执行什么操作
同步任务
执行任务会在当前线程中执行,当前线程有可能是主线程,也可能是子线程
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前block的任务
异步任务
执行任务,会在另外的线程执行,可能会创建新的任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 不用等待当前语句的执行完毕,就可以执行下一条语句
- 会开启新的线程执行block的任务
- 异步是多线程的代名词
GCD执行任务
1.GCD中有2个用来执行任务的函数
说明:把右边的参数(任务)提交给左边的参数(队列)进行执行。
(1)用同步的方式执行任务 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
参数说明:
queue:队列
block:任务
(2)用异步的方式执行任务 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
2.同步和异步的区别
同步:在当前线程中执行
异步:在另一条线程中执行
队列:用来存放任务
任务始终在主线程上执行,与更新UI相关的操作在主队列完成
并行队列
并行对列可以在多个线程之间分配执行,分配原则由GCD控制,因此并行队列的任务虽然先进先出原则,但由于分配到不同的线程中,因此完成时间可能不同即:后分配的有可可能先完成
串行队列
串行顺序是一个一个的完成,在一个线程执行 主队列:主队列也是串行队列,主队列任务都在主线程中完成
GCD的使用就2个步骤
(1)定制任务
(2)确定想做的事情
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出
GCD队列
GCD的队列可以分为2大类型
(1)并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
(2)串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
2.补充说明
有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行决定了任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
3.串行队列
GCD中获得串行有2种途径
(1)使用dispatch_queue_create函数创建串行队列
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 队列名称, 队列属性,一般用NULL即可
示例:
dispatch_queue_t queue = dispatch_queue_create(“wendingding”, NULL); // 创建
dispatch_release(queue); // 非ARC需要释放手动创建的队列
(2)使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
使用dispatch_get_main_queue()获得主队列
示例:
dispatch_queue_t queue = dispatch_get_main_queue();
4.并发队列
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags); // 此参数暂时无用,用0即可
示例:
这个参数是留给以后用的,暂时用不上,传个0。
第一个参数为优先级,这里选择默认的。获取一个全局的默认优先级的并发队列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列
说明:全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
5.各种队列的执行效果
任务与队列代码示例
异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。在大多数情况下,执行异步任务时会创建新的线程(在调用block代码块或开启定时器时一般是不会开启新的线程的),所以说线程的开启是和任务息息相关的。异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情
异步任务 + 并行队列
把异步任务放到并行队列进行执行,异步任务会在不同线程中执行,
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_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
开启了三条线程,异步任务在不同的线程中执行
异步任务 + 串行队列
特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
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_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
任务是按照顺序执行了只开启了一条子线程
异步任务 + 主队列
把异步任务放到主队列,由于主队列是一个特殊的串行队列,因此任务是串行执行的,但由于主队列对应1的线程,因此异步任务,也不会创建新的线程
主队列无论同步还是异步都不会开启新的线程,
并且任务按照顺序执行
需求:
iOS app在被用户切换到后台的时候就会处于挂起状态,如果这时你的应用正在进行一个任务中并且需要额外的时间来完成这个任务
调用两个方法里面的任何一个方法来延迟系统暂停你的应用,并申请额外的时间来完成未完成的任务。注意,当你完成任务后,必须调用endBackgroundTask:方法来告诉系统任务完成了,可以暂停应用了。或者,当你的应用回到前台了,就必须要结束这个任务了。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
__block typeof(self)bself = self;
dispatch_async(mainQueue, ^(void) {
if (bself != nil){
[[UIApplication sharedApplication] endBackgroundTask:bself.backgroundIdentifier];
bself.backgroundIdentifier = UIBackgroundTaskInvalid;
}
});
同步任务不会开启新的线程,按顺序执行,执行完一个再执行下一个,需要等待、协调运行;
同步任务 + 并行
同步任务的执行,是在当前线程中执行的,因此同步任务放在并列中执行,由于只有1个线程,任务也是一个一个按顺序执行(串行执行)的不会死锁
需求,删除数据库的数据
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
}
});
dispatch_sync(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
}
});
NSLog(@"syncConcurrent---end");
}
同步任务的执行,是在当前线程中执行的,
因此同步任务放在并列中执行,由于只有1个线程,任务也是一个一个按顺序执行(串行执行)的不会死锁
同步任务 + 串行队列
同步任务放在串行队列中执行,任务会在当前线程依次执行
需求:
保存数据到文件里面
NSString *filePath = @"文件地址";
dispatch_sync([self p_getFileHandleQueue], ^{
fileData = [NSData dataWithContentsOfFile:filePath];
});
- (dispatch_queue_t)p_getFileHandleQueue
{
static dispatch_queue_t queue = NULL;
static dispatch_once_t once;
dispatch_once(&once, ^{
//串行队列
queue = dispatch_queue_create("文件的名称", NULL);
});
return queue;
}
同步任务 + 主队列
这种情况,主线程会被阻塞,程序会挂死,不能使用
(4)用同步函数往串行队列中添加任务
(6)小结
说明:
- 同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;
- 异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。
同步函数
(1)并发队列:不会开线程
(2)串行队列:不会开线程
异步函数
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程
补充:
凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。
GCD的数据类型在ARC的环境下不需要再做release。
CF(core Foundation)的数据类型在ARC环境下还是需要做release。
异步函数具备开线程的能力,但不一定会开线程
相关面试题
GCD有哪些方法api
- 队列
dispatch_get_main_queue()
dispatch_get_global_queue()
dispatch_queue_create()
- 执行
dispatch_async()
dispatch_sync()
dispatch_after()
dispatch_once()
dispatch_apply()
dispatch_barrier_async()
dispatch_barrier_sync()
- 调度组
dispatch_group_create()
dispatch_group_async()
dispatch_group_enter()
dispatch_group_leave()
dispatch_group_notify()
dispatch_group_wait()
- 信号量
dispatch_semaphore_create()
dispatch_semaphore_wait()dispatch_semaphore_signal()
- 调度资源
dispatch_source_create()
dispatch_source_set_timer()
dispatch_source_set_event_handler()
dispatch_resume()
dispatch_suspend()
dispatch_source_cancel()
dispatch_source_testcancel()
dispatch_source_set_cancel_handler()
如何实现同步,有多少方式就说多少
- dispatch_sync()
- dispatch_barrier_sync()
- dispatch_group_create() + dispatch_group_wait()
- dispatch_apple()
- dispatch_semaphore_create() + dispatch_semaphore_wait()
- NSOpertaion start
- NSOperationQueue.maxConcurrentOperationCount = 1
- 锁 pthread_mutex
- NSLock
- NSRecursiveLock
- NSConditionLock & NSCondition
程序题1
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("question1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
// 异步
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案
1-5-2-4-3
分析
- DISPATCH_QUEUE_CONCURRENT 因为是并发队列所以不会阻塞
- 1 先打印,
- 5与2任务的复杂度是相同的,异步是耗时的所以5先执行然后执行2
- 3与4任务3在异步里面所以先执行4
思考
如果特殊情况2也可能先执行,比如当前主队列,卡顿延迟,回不来
程序题2
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("question1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案
1-5-2-3-4
分析
- 1 先打印,
- 5与2任务的复杂度是相同的,异步是耗时的所以5先执行然后执行2
- 3与4任务3在同步里面,同步是block先执行完才往下执行,所以3先执行完,4 在执行
思考
如果特殊情况2也可能先执行,比如当前主队列,卡顿延迟,回不来
程序题3
- (void)textDemo2{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("question2", NULL);
NSLog(@"1");
// 异步函数
dispatch_async(queue, ^{
NSLog(@"2");
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
答案
分析
- 这个是串行队列: #define DISPATCH_QUEUE_SERIAL NULL
- 先出来的是主队列的 1-5
- 然后输出 2
- 块任务添加3任务,然后3任务等待4任务执行完,4任务等待块任务执行完,块任务等待3任务执行完
- 这样就形成了死锁
延展
NSLog(@“4”); 把它注释还会死锁吗?
会。 因为等待的不是NSLog(@“4”) 而是dispatch_async(queue, ^{
NSLog(@“2”);
// 同步
dispatch_sync(queue, ^{
NSLog(@“3”);
});
NSLog(@“4”);
}
这个任务块执行完毕
程序题4
- (void)textDemo3{
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("question3", DISPATCH_QUEUE_CONCURRENT);
// 1,2 没有顺序
dispatch_async(queue, ^{ // 耗时
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
}
分析
-
1,2 没有顺序
-
0 优先于(7,8,9),(7,8,9)没有顺序
-
1,2,3 没有顺序,3堵塞的是 dispatch_sync(queue, ^{
NSLog(@“3”);
}) {} 之后,所以1,2,3没有顺序 -
0必须在3之后
GCD有哪些队列,默认提供哪些队列
主队列
`dispatch_get_main_queue()`
1.专门用来在线程上调度任务的串行队列
2.不会开启线程
3.如果当前主线程正有任务执行,那么无论主队列中添加了什么任务,都不会被调度
4.主队列创建在mian函数之前
全局并发队列
> `dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)`
>
> 队列优先级从高到底为:
>
> `DISPATCH_QUEUE_PRIORITY_HIGH`
>
> `DISPATCH_QUEUE_PRIORITY_DEFAULT`
>
> `DISPATCH_QUEUE_PRIORITY_LOW`
>
> `DISPATCH_QUEUE_PRIORITY_BACKGROUND`
- 全局队列是一个并发队列
- 在使用多线程开发时,如果对队列没有特殊要求,在执行异步任务时,可以直接使用全局队列
自定义队列(串行 Serial 与并行 Concurrent)
> `dispatch_queue_create("这里是队列名字", DISPATCH_QUEUE_SERIAL)`
>
> 串行 `DISPATCH_QUEUE_SERIAL`
>
> 并行 `DISPATCH_QUEUE_CONCURRENT`
同步还是异步都是耗时的
performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test) withObject:nil];
});
这里的 test 方法是不会去执行的,原因在于
performSelector 要创建任务是要提交到runLoop上去的
而 gcd 底层创建的线程是默认没有开启对应 runloop 的,所有这个方法就会失效。
解决办法
- dispatch_get_global_queue 改成主队列,由于主队列所在的主线程是默认开启了 runloop 的
- 就会去执行(将 dispatch_async 改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test