1. GCD简介
GCD
全称是Grand Central Dispatch
纯
C语言
,提供例如非常强大的函数GCD
作用:将任务添加到队列,并指定任务执行的函数
1.1 GCD
优势
GCD
是苹果公司为多核的并行运算提出的解决方案GCD
会自动利用更多的CPU
内核(比如双核、四核)GCD
会自动管理线程的生命周期(创建线程、调度任务、销毁线程)程序员只需要告诉
GCD
想要执行什么任务,不需要编写任何线程管理代码
1.2 GCD
基础
GCD
的基本用法
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
还原最基础的写法,可拆分成任务、队列和函数
- (void)syncTest{
// 任务
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
// 函数
dispatch_async(queue, block);
}
使用
dispatch_block_t
创建任务任务使用
block
封装任务的
block
没有参数,也没有返回值
使用
dispatch_queue_t
创建队列将任务添加到队列,并指定执行任务的函数
dispatch_async
2. 函数
在GCD
中执行任务的方式有两种,同步执行和异步执行
dispatch_sync
:同步函数dispatch_async
:异步函数
2.1 dispatch_sync
同步函数dispatch_sync
的特性:
必须等待当前语句执行完毕,才会执行下一条语句
不会开启线程,即不具备开启新线程的能力
在当前线程中执行block任务
2.2 dispatch_async
异步函数dispatch_async
的特性:
不用等待当前语句执行完毕,就可以执行下一条语句
会开启线程执行
block
任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)异步是多线程的代名词
2.3 二者的区别
是否等待队列的任务执行完毕
是否具备开启新线程的能力
3. 队列
队列分为串行队列和并发队列,用来存放任务。队列是一种数据结构,属于特殊的线性表,遵循先进先出(FIFC
)原则。新任务被插入到队尾,而任务的读取从队首开始。每读取一个任务,则队列中释放一个任务
在GCD
中,还提供了两个特殊的队列,分别是主队列和全局并发队列。主队列属于串行队列,而全局并发队列属于并发队列
队列和线程并没有关系,队列负责任务的调度,任务的执行依赖于线程,优先调度的任务不一定优先执行
3.1 串行队列
串行队列:Serial Dispatch Queue
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lg.serial", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lg.serial", DISPATCH_QUEUE_SERIAL);
每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个
只开启一个线程,同一时刻只调度一个任务执行
使用
DISPATCH_QUEUE_SERIAL
创建串行队列DISPATCH_QUEUE_SERIAL
也可传入NULL
,默认创建为串行队列
3.2 并发队列
并发队列:Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.concurrent", DISPATCH_QUEUE_CONCURRENT);
一次可以并发执行多个任务
开启多个线程,同一时刻可以调度多个任务执行
使用
DISPATCH_QUEUE_CONCURRENT
创建并发队列并发队列的并发功能只有在异步函数下才有效
3.3 主队列
主队列:Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
主队列:
GCD
提供的特殊的串行队列专门用来在主线程上调度任务的串行队列,依赖于主线程、主
Runloop
,在main
函数调用之前自动创建不会开启线程
如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
使用
dispatch_get_main_queue
获得主队列通常在返回主线程更新
UI
时使用
3.4 全局并发队列
全局并发队列:Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
GCD
提供的默认的并发队列为了方便开发者使用,苹果提供了全局队列
在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
使用
dispatch_get_global_queue
获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
参数1
表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT = 0
,被服务质量quality of service
取代参数2
为标记,是为了未来使用保留的。所以这个参数应该永远指定为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操作
});
});
4. 队列与函数的搭配使用
队列/函数 | 同步函数 | 异步函数 |
---|---|---|
串行队列 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
开启新线程 任务串行执行,一个接一个 |
并发队列 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
开启新线程 任务异步执行,没有顺序,和 CPU 调度有关 |
主队列 | 死锁 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
全局队列 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
开启新线程 任务异步执行,没有顺序,和 CPU 调度有关 |
4.1 串行队列
4.1.1 同步函数
// 同步串行队列
- (void)serialSyncTest{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<5; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
0:<NSThread: 0x2807e83c0>{number = 1, name = main}
1:<NSThread: 0x2807e83c0>{number = 1, name = main}
2:<NSThread: 0x2807e83c0>{number = 1, name = main}
3:<NSThread: 0x2807e83c0>{number = 1, name = main}
4:<NSThread: 0x2807e83c0>{number = 1, name = main}
hello queue
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.1.2 异步函数
// 异步串行队列
- (void)serialAsyncTest{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<5; i++) {
dispatch_async(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
hello queue
0:<NSThread: 0x282ba5740>{number = 5, name = (null)}
1:<NSThread: 0x282ba5740>{number = 5, name = (null)}
2:<NSThread: 0x282ba5740>{number = 5, name = (null)}
3:<NSThread: 0x282ba5740>{number = 5, name = (null)}
4:<NSThread: 0x282ba5740>{number = 5, name = (null)}
- 开启新线程
- 任务串行执行,一个接一个
4.2 并发队列
4.2.1 同步函数
// 同步并发队列
- (void)concurrentSyncTest{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<5; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
0:<NSThread: 0x2807e83c0>{number = 1, name = main}
1:<NSThread: 0x2807e83c0>{number = 1, name = main}
2:<NSThread: 0x2807e83c0>{number = 1, name = main}
3:<NSThread: 0x2807e83c0>{number = 1, name = main}
4:<NSThread: 0x2807e83c0>{number = 1, name = main}
hello queue
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.2.2 异步函数
// 异步并发队列
- (void)concurrentAsyncTest{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<5; i++) {
dispatch_async(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
hello queue
1:<NSThread: 0x2801ef300>{number = 10, name = (null)}
0:<NSThread: 0x2801ee300>{number = 8, name = (null)}
2:<NSThread: 0x2801e9b00>{number = 9, name = (null)}
3:<NSThread: 0x2801edf00>{number = 6, name = (null)}
4:<NSThread: 0x2801ee7c0>{number = 4, name = (null)}
- 开启新线程
- 任务异步执行,没有顺序,和
CPU
调度有关
4.3 主队列
4.3.1 同步函数
// 同步主队列
- (void)mainSyncTest{
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i<5; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//程序崩溃:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
主队列的作用,专门用来在主线程上调度任务的串行队列
主队列中增加同步函数,导致主线程需要等待同步函数完成后再执行
由于主队列是特殊的串行队列,同步函数需要等待主线程完成后再执行
所以,两个任务相互等待,产生死锁
4.3.2 异步函数
// 异步主队列
- (void)mainAsyncTest{
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i<5; i++) {
dispatch_async(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
hello queue
0:<NSThread: 0x280ed0200>{number = 1, name = main}
1:<NSThread: 0x280ed0200>{number = 1, name = main}
2:<NSThread: 0x280ed0200>{number = 1, name = main}
3:<NSThread: 0x280ed0200>{number = 1, name = main}
4:<NSThread: 0x280ed0200>{number = 1, name = main}
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.4 全局队列
4.4.1 同步函数
// 同步全局队列
- (void)globalSyncTest{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i<5; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
0:<NSThread: 0x283a948c0>{number = 1, name = main}
1:<NSThread: 0x283a948c0>{number = 1, name = main}
2:<NSThread: 0x283a948c0>{number = 1, name = main}
3:<NSThread: 0x283a948c0>{number = 1, name = main}
4:<NSThread: 0x283a948c0>{number = 1, name = main}
hello queue
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.4.2 异步函数
// 异步全局队列
- (void)globalAsyncTest{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i<5; i++) {
dispatch_async(queue, ^{
NSLog(@"%d:%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
-------------------------
//输出以下内容:
hello queue
0:<NSThread: 0x2813099c0>{number = 9, name = (null)}
1:<NSThread: 0x281372b80>{number = 4, name = (null)}
2:<NSThread: 0x281371e40>{number = 10, name = (null)}
3:<NSThread: 0x281372080>{number = 6, name = (null)}
4:<NSThread: 0x281372c00>{number = 3, name = (null)}
- 开启新线程
- 任务异步执行,没有顺序,和
CPU
调度有关
4.5 线程死锁
所谓线程死锁,是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
- 函数调用栈中,
_dispatch_sync_f_slow
即为死锁的异常
5. 面试题解析
5.1 题目1
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"2:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"3:%@",[NSThread currentThread]);
});
NSLog(@"4:%@",[NSThread currentThread]);
});
NSLog(@"5:%@",[NSThread currentThread]);
}
-------------------------
//输出以下内容:
1:<NSThread: 0x2801a4900>{number = 1, name = main}
5:<NSThread: 0x2801a4900>{number = 1, name = main}
2:<NSThread: 0x2801e0580>{number = 6, name = (null)}
4:<NSThread: 0x2801e0580>{number = 6, name = (null)}
3:<NSThread: 0x2801e0580>{number = 6, name = (null)}
5.2 题目2
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"2:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"3:%@",[NSThread currentThread]);
});
NSLog(@"4:%@",[NSThread currentThread]);
});
NSLog(@"5:%@",[NSThread currentThread]);
}
-------------------------
//输出以下内容:
1:<NSThread: 0x281864900>{number = 1, name = main}
5:<NSThread: 0x281864900>{number = 1, name = main}
2:<NSThread: 0x28185ad40>{number = 5, name = (null)}
3:<NSThread: 0x28185ad40>{number = 5, name = (null)}
4:<NSThread: 0x28185ad40>{number = 5, name = (null)}
5.3 题目3
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
NSLog(@"1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"2:%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"3:%@",[NSThread currentThread]);
});
NSLog(@"4:%@",[NSThread currentThread]);
});
NSLog(@"5:%@",[NSThread currentThread]);
}
-------------------------
//输出以下内容:
1:<NSThread: 0x281afc900>{number = 1, name = main}
5:<NSThread: 0x281afc900>{number = 1, name = main}
2:<NSThread: 0x281abdcc0>{number = 6, name = (null)}
//程序崩溃:
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
1
和5
在主队列,不受影响异步函数先执行
2
,但是4
要等待同步函数优先执行同步函数的特性“护犊子”,必须自身优先执行,然后才会执行下面的代码。所以异步函数的完成,必须等待同步函数先执行完成
由于使用串行队列,同步函数又需要代码异步函数执行完成后才能执行
所以,两个函数相互等待,产生死锁
5.4 题目4
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", 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");
});
}
A:1230789
B:1237890
C:3120798
D:2137890
正确答案:A、C
3
在同步函数中,0
在主线程,所以3
一定在0
的前面1
和2
在异步函数中,所以顺序不一定7
、8
、9
一定会在3
和0
之后,但它们在异步函数中,所以顺序不一定
5.5 题目5
队列的类型有几种?
//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.lg.cn", NULL);
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
正确答案:两种
串行队列:
serialQueue
、mainQueue
并发队列:
concurrentQueue
、globalQueue
5.6 题目6
@property (atomic, assign) int num;
- (void)wbinterDemo{
while (self.num < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num++;
});
}
NSLog(@"end : %d",self.num);
}
- 正确答案:
>= 5
,虽然num
使用atomic
修饰,但只能保证在自身线程中是安全的。当开启异步多线程,在while
停止循环后,可能还会有线程进行num++
,所有最终输出的结果大概率会
5.7 题目7
- (void)wbinterDemo{
for (int i= 0; i<10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.num++;
});
}
NSLog(@"end : %d",self.num);
}
- 正确答案:
<= 10000
总结
GCD
简介:
GCD
全称是Grand Central Dispatch
- 纯
C语言
,提供例如非常强大的函数 GCD
作用:将任务添加到队列,并指定任务执行的函数
GCD
优势:
GCD
是苹果公司为多核的并行运算提出的解决方案GCD
会自动利用更多的CPU
内核(比如双核、四核)GCD
会自动管理线程的生命周期(创建线程、调度任务、销毁线程)- 程序员只需要告诉
GCD
想要执行什么任务,不需要编写任何线程管理代码
GCD
基础:
- 使用
dispatch_block_t
创建任务- 任务使用
block
封装 - 任务的
block
没有参数,也没有返回值
- 任务使用
- 使用
dispatch_queue_t
创建队列 - 将任务添加到队列,并指定执行任务的函数
dispatch_async
函数:
- 在
GCD
中执行任务的方式有两种,同步执行和异步执行dispatch_sync
:同步函数dispatch_async
:异步函数
队列:
- 队列分为串行队列和并发队列,用来存放任务。队列是一种数据结构,属于特殊的线性表,遵循先进先出(
FIFC
)原则。新任务被插入到队尾,而任务的读取从队首开始。每读取一个任务,则队列中释放一个任务- 串行队列:
Serial Dispatch Queue
- 每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个
- 只开启一个线程,同一时刻只调度一个任务执行
- 使用
DISPATCH_QUEUE_SERIAL
创建串行队列 DISPATCH_QUEUE_SERIAL
也可传入NULL
,默认创建为串行队列
- 并发队列:
Concurrent Dispatch Queue
- 一次可以并发执行多个任务
- 开启多个线程,同一时刻可以调度多个任务执行
- 使用
DISPATCH_QUEUE_CONCURRENT
创建并发队列 - 并发队列的并发功能只有在异步函数下才有效
- 串行队列:
- 在
GCD
中,还提供了两个特殊的队列,分别是主队列和全局并发队列。主队列属于串行队列,而全局并发队列属于并发队列- 主队列
- 主队列:
GCD
提供的特殊的串行队列 - 专门用来在主线程上调度任务的串行队列,依赖于主线程、主
Runloop
,在main
函数调用之前自动创建 - 不会开启线程
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
- 使用
dispatch_get_main_queue
获得主队列 - 通常在返回主线程更新
UI
时使用
- 主队列:
- 全局并发队列
GCD
提供的默认的并发队列- 为了方便开发者使用,苹果提供了全局队列
- 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
- 使用
dispatch_get_global_queue
获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
- 主队列
- 队列和线程并没有关系,队列负责任务的调度,任务的执行依赖于线程,优先调度的任务不一定优先执行
队列与函数的搭配使用:
- 串行队列
- 同步函数
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
- 会产生堵塞
- 异步函数
- 开启新线程
- 任务串行执行,一个接一个
- 同步函数
- 并发队列
- 同步函数
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
- 异步函数
- 开启新线程
- 任务异步执行,没有顺序,和
CPU
调度有关
- 同步函数
- 主队列
- 同步函数
- 死锁
- 异步函数
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
- 同步函数
- 全局队列
- 同步函数
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
- 异步函数
- 开启新线程
- 任务异步执行,没有顺序,和
CPU
调度有关
- 同步函数