多线程是iOS面试的基础和重点问题,多线程的问题如果没答好面试直接就没戏了。本文将从面试角度对多线程部分进行梳理,持续更新。

进程、线程与队列

进程与线程

  • 进程是系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在专用且受保护的内存空间内。
  • 线程是进程的基本执行单元,一个进程中的任务都在线程中执行,所以一个进程由至少一个线程组成。

多线程

一个线程中的任务是串行的,同一时间内,一个线程只能执行一个任务。一个进程可以开启多条线程,每条线程可以并行执行不同的任务。同一时间,CPU只能处理一条线程,多线程并发执行是CPU快速地在多条线程之间调度。

iOS的多线程技术有:pthreadNSThreadGCDNSOperation

主线程

主线程主要负责显示和刷新UI界面,处理UI事件。

队列的类型

  • 主队列:dispatch_get_main_queue
  • 全局并发队列:dispatch_get_global_queue
  • 自己创建队列:dispatch_queue_create

线程通信

GCD:

1
2
3
4
5
6
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 在这里执行耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
});
});

NSOperationQueue:

1
2
3
4
5
6
[[NSOperationQueue new] addOperationWithBlock:^{
NSLog(@"子线程下载: %@", NSOperationQueue.currentQueue);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"主线程刷新UI: %@", NSOperationQueue.currentQueue);
}];
}];

死锁

死锁就是队列引起的循环等待:在串行队列A中向队列A添加一个同步任务,例如主队列同步:

1
2
3
4
5
6
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{ // 👈死锁在这一行
// NSLog(@"在主队列同步执行");
});
}

或者在自定义线程中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// NOT OK
- (void)test1 {
NSLog(@"在主线程添加一个串行队列queue");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"在串行队列queue中添加一个同步任务");
dispatch_sync(queue, ^{ // 👈死锁在这一行
NSLog(@"OK");
});
});
}

// 这样就不会死锁了
- (void)test2 {
NSLog(@"在主线程添加一个串行队列queue");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"在串行队列queue2中添加一个同步任务");
dispatch_sync(queue2, ^{
NSLog(@"OK");
});
});
}

线程管理

设置最大并发数

NSOperationQueue:

1
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

设置依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"下载图片1");
}];
NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"下载图片2");
}];
NSOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"合成图片");
}];
[combine addDependency:op1];
[combine addDependency:op2];

[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:combine];

设置栅栏

若干个网络请求结束后执行下一步操作:

1
2
3
4
5
6
7
8
9
10
11
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_async(group, queue, ^{
sleep(1);
NSLog(@"网络请求 ---- %d",i);
});
}
dispatch_barrier_sync(queue, ^{
NSLog(@"主线程刷新UI");
});

通过NSOperationQueue实现:

1
2
3
4
5
6
7
8
9
10
11
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++) {
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"网络请求 ---- %d",i);
}];
[queue addOperation:op];
}
[queue addBarrierBlock:^{
NSLog(@"刷新页面");
}];

线程组

在n个耗时并发任务都完成后,再去执行接下来的任务。比如,在n个网络请求完成后去刷新UI页面。

1
2
3
4
5
6
7
8
9
10
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_async(group, queue, ^{
NSLog(@"网络请求 ---- %d",i);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新页面");
});

GCD和NSOperation有什么区别

  • GCD是纯C语言的API;NSOperation是基于GCD的OC版本封装
  • GCD只支持FIFO的队列;NSOperation可以很方便地调整执行顺序,设置最大并发数量
  • NSOperationQueue可以轻松在operation间设置依赖关系,而GCD需要些很多代码才能实现
  • NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinish),是否取消(isCancel)
  • GCD的执行速度比NSOperation快

 评论