通过上一个章节,我们已经了解了一些多线程的基本知识。本章就来讲解在平时开发中,最为常用的
GCD
的一些知识
系列文章传送门:
GCD简介
定义
GCD
全程为Grand Central Dispatch
,由C语言
实现,是苹果为多核的并行运算提出的解决方案,CGD
会自动利用更多的CPU内核,自动管理线程的生命周期,程序员只需要告诉GCD
需要执行的任务,无需编写任何管理线程的代码。GCD
也是iOS使用频率最高的多线程技术。
优势
GCD
是苹果公司为多核的并行运算提出的解决方案GCD
会自动利用更多的CPU内核(比如双核、四核)GCD
会自动管理线程的生命周期(创建线程、调度任务、销毁线程)- 程序员只需要告诉
GCD
想要执行什么任务,不需要编写任何线程管理代码
GCD使用
GCD
的使用API
较为简单,主要分为同步和异步执行。在上一章我们对概念已经有所了解,就不做赘述了。
同步执行API
//queue:队列 block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步执行API
//queue:队列 block:任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
我们发现在使用GCD
时,需要传入两个参数。分别是dispatch_queue_t
代表添加的队列类型,dispatch_block_t
为需要执行的任务,下面我们来看一下不同参数的运行效果。
dispatch_block_t
任务
通过查看其定义可得,该参数其实就是一个没有参数的block
回调,用来执行任务的。
typedef void (^dispatch_block_t)(void);
dispatch_queue_t
队列
dispatch_queue_t
参数表示队列的类型。根据上一章节我们知道,队列(FIFO
)在iOS中主要一下分为4种:
- 主队列(
main_queue
):由系统创建的串行队列。- 获取方式:
dispatch_get_main_queue()
- 获取方式:
- 全局队列(
global_queue
):由系统创建的并发队列- 获取方式:
dispatch_get_global_queue(long identifier, unsigned long flags);
- 获取方式:
- 串行队列(
Serial Dispatch Queue
):自定义的串行队列- 获取方式:
dispatch_queue_create(@"队列名",DISPATCH_QUEUE_SERIAL)
- 获取方式:
- 并发队列(
Concurrent Dispatch Queue
):自定义的并发队列- 获取方式:
dispatch_queue_create(@"队列名",DISPATCH_QUEUE_CONCURRENT);
- 获取方式:
其中,全局队列根据入参的不同,会获取到不同的队列,在日常的开发中,我们一般入参0
,来获取到主并发队列。
自定义的队列都是调用dispatch_queue_create(const char *_Nullable label,dispatch_queue_attr_t _Nullable attr)
方法来进行创建,两个参数分别为自定义队列名称
和队列类型
。其中串行队列DISPATCH_QUEUE_SERIAL
为宏定义的NULL
,所以传NULL
也表示为串行队列。
现在,我们来看这2种执行模式,3种队列是相互搭配是什么效果
同步 +(主队列或者自定义串行队列)
相关代码如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"任务2",[NSThread currentThread]);
});
NSLog(@"任务3:%@",[NSThread currentThread]);
}
运行结果
打印出任务1后,程序死锁崩溃
为什么会造成以上的结果呢?
首先分析一下代码:主线程执行完任务1
后,在主队列dispatch_get_main_queue()
中同步执行(dispatch_sync)
任务2,然后执行任务3。
队列的特点是FIFO
,主队列中已经存在任务viewDidLoad
,往主队列加入任务2,就需要执行完viewDidLoad
才能执行任务2。但是想要执行完viewDidLoad
又必须先执行viewDidLoad
内的任务2和任务3。这就造成了死锁。

异步 +(主队列或者自定义串行队列)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1:%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"任务2:%@",[NSThread currentThread]);
});
NSLog(@"任务3:%@",[NSThread currentThread]);
}
运行结果

我们可以看到当获取到主队列后,使用异步执行的方式,不会造成程序的崩溃。
因为主线程执行任务1之后,需要异步(dispatch_async)
执行任务2;而dispatch_async
不要求立马在当前线程同步执行任务,也就是不会堵塞;所以主线程接着执行任务3,最后异步执行任务2。
同步+(全局队列或者自定义并发队列)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务一:%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务二:%@",[NSThread currentThread]);
}
});
}
运行结果

异步+(全局队列或者自定义并发队列)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务一:%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i<3; i++) {
NSLog(@"任务二:%@",[NSThread currentThread]);
}
});
}
运行结果:

可以看出任务1和任务2交错执行,并非同步执行那样执行完任务1再执行任务2。而且通过线程编号可以看出,的确开启了新的线程。说明异步执行具备开启新线程的能力。
但是通过上面异步+主队列
的打印结果我们可以发现,在主队列时,异步执行也并没有开启新的线程,而仍然是同一个主线程。说明如果在主队列中异步执行,是不会开启新线程的
原因是因为主队列是串行队列,必须执行完一个任务再执行另一个任务,异步执行只是没有造成堵塞,但是在主队列还是是要一步步走的。
同一串行队列嵌套
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果
任务1,任务5,任务2,然后程序崩溃
这里造成死锁崩溃的原因和上面同步+串行队列
的原因一样。由于主队列是串行的,所以代码必然是从上到下依次执行的。
打印完任务1后,执行dispatch_async
为异步,不会阻塞线程,但是会加入自定义的串行队列中,所以会执行任务5,接着执行异步代码块中逻辑,打印任务2,接着执行dispatch_sync
代码块,但是由于其是同步,串行队列中已经有了dispatch_async
任务未执行完毕,此时dispatch_sync
阻塞当前线程等待任务3执行,造成了相互等待,所以就死锁崩溃了

不同串行队列嵌套
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue2, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果
任务1,任务5,任务2,任务3,任务4
通过运行我们可以发现,当嵌套执行时,同步异步在不同的串行队列执行,并不会造成死锁崩溃。而是按照串行的顺序,执行代码。这事因为dispatch_sync
阻塞的并不是当前的线程,而是其他的线程,所以不会造成死锁等待。

同一并发队列嵌套
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果:
任务1,任务5,任务2,任务3,任务4
主线程执行任务1之后,需要异步dispatch_async
执行任务2;所以先执行主线程的任务5,然后执行任务2;接着需要在并发队列中同步dispatch_sync
执行任务3,所以会造成阻塞,等待其执行完成,然后执行并发队列中的任务4。

不同并发队列嵌套
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"任务1");
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue2, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
运行结果:
任务1,任务5,任务2,任务3,任务4
这个代码的分析和不同串行队列嵌套
的类似,创建了新的队列,所以是不会造成当前队列的阻塞的。

总结

- 同步执行没有开辟线程能力,且代码顺序执行,会产生堵塞
- 同步执行在同一串行队列时,会造成死锁等待
- 异步执行具有开辟线程的能力,在并发队列执行时,执行顺序不确定,在串行队列执行时,按照顺序执行,且主队列时不会开辟新线程