GCD
- GCD全程为Grand Centeral Dispatch,翻译成中文为重要的中央调度方案;
- 纯C语言实现是苹果公司为
多核的并行运算
提出的解决方案; - GCD会自动利用更多的CPU内核(比如双核、四核);
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- GCD实现的就是:将任务添加到队列,并指定任务执行的函数。
GCD的使用
- 通常我们使用GCD通常会这么写代码:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@" currentThread = %@",[NSThread currentThread]);
});
- 现在我们将上面的进行拆分:由任务 + 队列 + 函数三个部分构成
//队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//任务
dispatch_block_t taskBlock = ^{
NSLog(@" currentThread = %@",[NSThread currentThread]);
};
//将任务添加到队列,并指定函数执行
dispatch_async(queue, taskBlock);
- 使用dispatch_block_t 创建任务
- 使用dispatch_queue_t 创建队列
- 将任务添加到队列,并指定执行任务的函数dispatch_async
函数
dispatch_sync函数
- 同步执行函数,会阻塞当前线程的
继续执行
; - 必须等待队列中正在执行的任务执行完毕时,才会执行下一个任务;
- 不具备开启新线程的能力,在当前线程中执行block任务;
- 会立即去执行block任务;
dispatch_async函数
- 异步执行函数,不会阻塞当前线程的继续执行;
- 不用等待队列中正在执行的任务执行完毕,就可以执行下一个任务;
- 具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关),则可能会开启新的线程执行block任务;
- 不会立即去执行block任务,延迟执行;
队列
- iOS多线程中所说的队列(Dispatch Queue)是指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取。每读取一个任务,则在队列中释放一个任务;
串行队列(serial queue)
- 每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程(通俗理解:同一时刻只调度一个任务执行);
- 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL),创建串行队列
其中的DISPATCH_QUEUE_SERIAL也可以使用NULL表示,这两种均表示默认的串行队列
dispatch_queue_t serial_one = dispatch_queue_create("com.lyy.queue_one", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serial_two = dispatch_queue_create("com.lyy.queue.two", NULL);
并发队列(coucurrent queue)
- 一次可以并发执行多个任务,即开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行);
- 使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT)创建并发队列;
- 并发队列的并发功能只有在
异步函数
下才有效;
dispatch_queue_t coucurrent = dispatch_queue_create("com.lyy.queue_three", DISPATCH_QUEUE_CONCURRENT);
主队列(Main Dispatch Queue)
- GCD中提供的特殊的串行队列;
- 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建;
- 不会开启线程;
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度;
- 使用dispatch_get_main_queue()获得主队列;
- 通常在返回主线程 更新UI时使用;
dispatch_queue_t mainQueue = dispatch_get_main_queue();
全局队列(Global Dispatch Queue)
- GCD提供的默认的并发队列;
- 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列;
- 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
- 第一个参数表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在iOS9之后,已经被服务质量(quality-of-service)取代;
- 第二个参数使用0;
dispatch_queue_t global_queue = dispatch_get_global_queue(0, 0);
//优先级从高到低(对应的服务质量)依次为
- 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
全局队列与主队列的组合使用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//执行耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
//回到主线程进行UI操作
});
});
函数与队列的组合使用
【第一种:同步串行】
- 不会开辟新的线程,任务在当前主线程依次执行。
【第二种:异步串行】
- 只开辟一条新的子线程,任务在新的子线程上依次执行;
【第三种:同步并发】
- 由于同步不能开辟新的子线程,所以所有任务都在当前主线程上依次执行;
【第四种:异步并发】
- 因为异步能开辟子线程,至于开辟多少条子线程是由操作系统决定的,任务在多个子线程上无序执行;
【第五种:同步主队列】
同步主队列造成死锁,应用程序崩溃;
viewDidLoad
函数在主线程中执行,执行到3的位置才会执行完毕;当执行到
dispatch_sync
函数时,向主队列中添加block任务,然后再取出block任务在主线程上执行且会阻塞主线程
,当要执行block任务时,viewDidLoad
已经在主线程中执行,必须等其执行完成才会执行block任务,而viewDidLoad
要执行完成必须要等block任务执行完才能执行,造成相互等待,形成死锁;-
总结:
- 主线程中的
viewDidLoad
与taskBlock两个任务相互等待,形成死锁;另一种解释就是当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度;
- 主线程中的
【第六种:异步主队列】
- 因为异步不会阻塞当前主线程,
viewDidLoad
立马执行完成; - 因为主队列的原因,不会开辟子线程,主队列中任务都在主线程上依次执行;
【第七种:同步全局队列】
- 同步-->主线程阻塞,
主线程切换到全局队列
上依次执行全局队列中的任务,全局队列中的任务执行完成再执行主线程中任务
【第八种:异步全局队列】
- 异步全局不阻塞当前线程,且开辟多条子线程执行全局队列中的任务,任务无序执行;
代码案例
案例一
- (void)viewDidLoad {
[super viewDidLoad];
//并行队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{//block1
NSLog(@"2");
dispatch_async(queue, ^{//block2
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- LLDB调试结果:
- 主线程的任务队列为:任务1、异步函数1、任务5,其中异步函数中的block1会延迟执行,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步block1执行;
- 在执行异步函数1时,往并发队列中追加任务block1(任务2,异步函数2,任务4),且开辟子线程1执行任务,其中异步函数2中的block2会延迟执行,任务2和任务4是复杂度一样,所以任务2和任务4优先于异步函数2执行;
- 在执行异步函数2时,往并发队列中追加任务3,且开辟子线程2,然后子线程2执行任务3;
案例二
- (void)viewDidLoad {
[super viewDidLoad];
//并发队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
//异步函数
dispatch_async(queue, ^{//block1
NSLog(@"2");
//同步函数
dispatch_sync(queue, ^{//block2
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- LLDB调试结果:
- 主线程的任务队列为:任务1、异步函数、任务5,其中异步函数中的block1会延迟执行,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步函数执行;
- 在执行异步函数时,往
并发队列
中追加任务block1(包括任务2,同步函数与任务4),然后开辟子线程1执行任务block1,先执行任务2, - 接着遇到dispatch_sync同步,会阻塞当前子线程1的继续执行,且往
并发队列
中追加任务3,由于是在并发队列中,任务3无需等待block1执行完就可以执行
(block1任务执行了一半,子线程切换执行任务3),当任务3执行完成时,当前子线程1会解除阻塞,开始执行任务4;
案例三
- (void)viewDidLoad {
[super viewDidLoad];
//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);
NSLog(@"1");
//异步函数
dispatch_async(queue, ^{//block1
NSLog(@"2 --- %@",[NSThread currentThread]);
//同步函数
dispatch_sync(queue, ^{//block2
NSLog(@"3 --- %@",[NSThread currentThread]);
});
NSLog(@"4 --- %@",[NSThread currentThread]);
});
NSLog(@"5");
}
- LLDB调试结果:
- 主线程的任务队列为:任务1、异步函数、任务5,其中异步函数中的block1会延迟执行,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步函数执行;
- 在执行异步函数时,往串行队列中追加任务block1(包括任务2,同步函数与任务4),然后开辟子线程1执行任务block1,先执行任务2;
- 接着遇到dispatch_sync同步,会阻塞当前子线程1的继续执行,且往
串行队列
中追加任务3,由于是在串行队列中,block1已经在执行了但没有执行完成,所以必须要等block1执行完成才会执行任务3
,即任务3等待block1; 而block1要执行完成(即任务4执行完),必须任务3执行完成解除当前子线程1阻塞才会执行任务4,即block1等待任务3;任务3与block1相互等待,形成死锁;
案例四
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0,0),^{//block1
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(),^{//block2
NSLog(@"3 -- %@",[NSThread currentThread]);
});
NSLog(@"4");
});
NSLog(@"5");
}
- LLDB调试结果:
- 主线程的任务队列为:任务1、异步函数、任务5,其中异步函数中的block1会延迟执行,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步函数执行;
- 在执行异步函数时,往全局并发队列中追加任务block1(包括任务2,同步函数与任务4),然后开辟子线程1执行任务block1,先执行任务2;
- 接着遇到dispatch_sync同步,会阻塞当前子线程1的继续执行,且往主队列中追加任务3,当前主线程没有阻塞可以正常执行任务3,当任务3在主线成中执行完成时,当前子线程1会解除阻塞,接着执行任务4;
案例五
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0,0),^{//block1
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(),^{//block2
NSLog(@"2");
});
NSLog(@"3");
});
NSLog(@"4");
while(1){
}
NSLog(@"5");
}
- LLDB调试结果:
- 主线程队列中追加任务:异步函数,任务4,死循环,任务5,其中异步函数中的block1会延迟执行,所以任务4率先执行,遇到死循环,出现主线程阻塞,任务5永远执行不了;
- 在执行异步函数时,往全局并发队列中追加block1任务(任务1,同步函数,任务3),然后开辟子线程1,执行开始执行block1,首先执行任务2;
- 接着遇到同步函数,会阻塞当前子线程1且往主队列中追加任务2,由于主线程死循环阻塞了,任务2无法执行,导致子线程1一直阻塞,任务3也无法执行;
案例六
- (void)viewDidLoad {
[super viewDidLoad];
//并发队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
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");
});
}
- 执行结果:0一定在3之后,且在789之前;