一、iOS中的常见多线程方案
| 技术方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
|---|---|---|---|---|
| pthread | 1.一套通用的多线程API 2.适用于Unix/Linux/Windows等系统 3.跨平台/可移植 4.使用难度大 |
C | 程序员管理 | 几乎不用 |
| NSThread | 1.使用更加面向对象 2.简单易用,可直接操作线程对象 |
OC | 程序员管理 | 偶尔使用 |
| GCD | 1.旨在替代NSThread等线程技术 2.充分利用设备多核 |
C | 自动管理 | 经常使用 |
| NSOperation | 1.基于GCD(底层是GCD) 2.比GCD多了一些更简单实用的功能 3.使用更加面向对象 |
OC | 自动管理 | 经常使用 |
二、相关术语
- 在多线程中,有4个术语比较重要,同样也比较容易混淆:同步 异步 并发 串行
- 同步和异步主要影响:能不能开启新的线程
- 同步:不具备开启新线程的能力,在当前线程中执行任务。
- 异步:具备开启新线程的能力,但是不代表异步一定可以开启新的线程(如果队列是主队列,就不会开启新的线程)
- 并发和串行主要影响:任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务
- 同步和异步主要影响:能不能开启新的线程
三、GCD的常用函数
-
GCD中有两个用来执行任务的函数
-
用同步的方式执行任务
- dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
-
用异步的方式执行任务
- dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
-
-
GCD是开源的:GCD源码
四、GCD的队列
- GCD的队列可以分为2大类型
- 并发队列(Concurrent Dispatch Queue)
- 可以让多个任务**并发(同时)**执行(自动开启多个线程同时执行任务)
- 并发只有在异步(dispatch_async)函数下才有效
- 串行队列(Serial Dispatch Queue)
- 让任务一个接着一个的执行(一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue)
五、各种队列的执行效果
| 并发队列(获取全局并发队列/手动创建的并发队列) | 手动创建的串行队列 | 主队列 | |
|---|---|---|---|
| 同步(sync) | 1. 不会开启新线程 2.串行执行任务 |
1. 不会开启新线程 2.串行执行任务 |
1.不会开启新线程 2.串行执行任务 |
| 异步(async) | 1.会开启新线程 2.并发执行任务 |
1.会开启新线程 2.串行执行任务 |
1.不会开启新线程 2.串行执行任务 |
- 注意:使用sync函数往当前串行队列中添加任务,会卡主当前串行队列(死锁),后面会举例说明。
六、举个栗子
6.1 基本演练(不包含主队列)
- 并发队列有2种创建的方式(下面的栗子中使用手动创建的方式)
- 获取全局的并发队列(系统为我们创建的)
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); - 手动创建并发队列(手动创建的)
dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 获取全局的并发队列(系统为我们创建的)
- 同步并发队列
- 在当前线程执行任务(主线程)
- 任务串行执行(先执行任务1,再执行任务2)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务1 - %@", [NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务2 - %@", [NSThread currentThread]);
}
});
}
打印结果:
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
- 同步串行队列
- 在当前线程执行(主线程)
- 串行执行任务(先执行任务1,再执行任务2)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务1 - %@", [NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务2 - %@", [NSThread currentThread]);
}
});
}
打印结果:
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
- 异步并发队列
- 在子线程执行
- 同时执行
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务1 - %@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务2 - %@", [NSThread currentThread]);
}
});
}
打印结果:
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
- 异步串行队列
- 在子线程执行
- 串行执行任务(先执行任务1,再执行任务2)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务1 - %@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务2 - %@", [NSThread currentThread]);
}
});
}
打印结果:
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
6.2、主队列(特殊的串行队列)
- 异步主队列
- 主线程执行(主队列jishi)
- 串行执行
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务1 - %@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务2 - %@", [NSThread currentThread]);
}
});
}
打印结果:
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
- 同步主队列
- 死锁
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{ // 任务1
for (int i = 0; i < 5; i++) {
NSLog(@"执行任务1 - %@", [NSThread currentThread]);
}
});
}
-
首先理解队列、串行队列和并发队列
- 队列:FIFO即First In First Out -> 先进先出
- 串行队列:队列中的任务,当一个任务执行完,下一个任务才执行。
- 并发队列:队列中的任务,会同时执行
-
上面的代码中,同步主队列为什么会死锁
- 主队列是一个串行队列,先进入队列中的是viewDidLoad任务
- 在viewDidLoad任务后面,要同步执行一个任务1,即在当前线程(主线程)执行任务1,因为是同步串行执行任务,就要一个任务执行完,再继续执行下一个任务的执行
- 因为viewDidLoad还没要执行完,所以任务1就没办法进行执行,而viewDidLoad想要执行完,就必须要等任务1执行完。这就形成了一种互相等待的状态。所以产生了死锁的现象。
七、相关面试题
7.1 以下代码会不会发生死锁现象?为什么?
- 会,死锁的原因就是上面解释的主队列同步执行任务的原因
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin ------------");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务1");
});
NSLog(@"end ------------");
}
打印结果:
begin ------------
崩溃(死锁)
7.2 以下代码会不会发生死锁现象?为什么?
- 不会,因为任务1是异步执行的,在主队列(串行队列)中,会同时执行viewDidLoad和任务1两个任务
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin ------------");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务1");
});
NSLog(@"end ------------");
}
打印结果:
begin ------------
end ------------
执行任务1
7.3 以下代码会不会发生死锁现象?为什么?
- 会,因为子线程中的任务是在串行队列中执行的,在这个子线程中又在当前串行队列中进行同步操作,那么在串行队列中,就会形成子线程中的任务和同步任务互相等待的现象,所以会死锁。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin ------------");
dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行任务1");
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
});
NSLog(@"end ------------");
}
打印结果:
begin ------------
end ------------
执行任务1
崩溃(死锁)
7.4 以下代码会不会发生死锁现象?为什么?
- 不会,因为在子线程的串行队列中是异步执行任务
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin ------------");
dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"执行任务1");
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
});
NSLog(@"end ------------");
}
总结:使用sync函数往当前串行队列中添加任务,就会产生死锁。
八、队列组的使用(补充)
如何用GCD实现以下需求:
需求1:异步执行任务1和任务2,等任务1和任务2执行完毕后,回到主线程执行任务3
完成上面的需求,就需要使用队列组的技术
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 任务1
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1 - %@", [NSThread currentThread]);
}
});
// 任务2
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2 - %@", [NSThread currentThread]);
}
});
// 任务3
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3 - %@", [NSThread currentThread]);
}
});
// 打印结果:
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
通过打印的结果发现,任务1和任务2异步执行,任务1 和任务2执行完毕后,在主线程执行任务3,通过使用dispatch_group_notify方法通知队列组在执行完任务1和任务2之后,自动执行任务3。
需求二:异步执行任务1和任务2,等任务1和任务2执行完毕后,异步执行任务3和任务4
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 任务1
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务1 - %@", [NSThread currentThread]);
}
});
// 任务2
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务2 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务3 - %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"任务4 - %@", [NSThread currentThread]);
}
});
打印结果:
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
从打印结果中可以发现,先异步执行任务1和任务2,等任务1 和任务2执行完毕后,异步执行任务3和任务4。通过dispatch_group_notify来进行通知处理。