1、进程和线程的区别?

(1)进程是个静态的容器,可以理解为正在执行的应用程序实例,它里面容纳了很多个线程,线程则是一系列方法的线性执行路径(CPU调度的基本单位)。

(2)进程拥有独立的资源空间(资源分配基本单位),共享起来比较复杂,常使用IPC方式进行同步,同步起来简单,线程间共享所属进程空间,资源共享简单但同步复杂,常使用加锁等方式进行同步。

(3)进程崩溃不会影响其他进程,一个线程崩溃则会导致整个进程崩溃

2、iOS中多线程有几种实现方式?分别有什么区别?

(1)pthread(POSIX Threads):一套C语言编写的跨平台多线程API,使用难度大,需要手动管理线程生命周期。

(2)NSThread:面向对象操作线程,使用相对简单,需要手动管理线程生命周期。

(3)GCD:苹果多核编程解决方案,使用起来非常方便。需要自己实现如:限制并发数,任务间的依赖等功能。自动管理线程生命周期。

(4)NSOperation:基于GCD的封装,面向对象操作线程,提供了比GCD更丰富的API:限制最大并发数,设置任务依赖关系。但是它不能直接使用,因为它是一个抽象类,可以继承它或者使用系统定义NSInvocationOperation或NSBlockOperation。自动管理线程生命周期。

3、NSOperationQueue和GCD有什么区别?

(1) GCD底层是C语言构成的API。NSOperationQueue及相关对象是Objc对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构。而NSOperation作为一个对象,为我们提供了更多的选择。

(2) 在NSOperationQueue中,取消任务非常方便,而GCD没法停止已经加入queue的block。

(3) NSOperation能够方便的设置依赖关系,还能设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行。在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务优先级也需要大量复杂代码。
NSOperationQueue还可以设置最大并发数,GCD则需要自己实现。

(4)NSOperation任务状态属性支持KVO,可以通过KVO来监听operation的就绪、取消、执行中、执行完成等状态。GCD则无法判断当前任务执行状态。

4、iOS中线程间如何通信?

线程间通信主要是线程间的同步:

(1)GCD:dispatch_group_t、dispatch_barrier、dispatch_semaphore

(2)NSOperationQueue:任务间添加依赖关系控制同步

(3)线程锁:NSLock、NSRecursiveLock等

涉及到线程异步数据传递:

(1)Mach port:NSMachPort结合RunLoop实现

  • 创建NSMachPort对象并设置代理

  • 将NSMachPort对象添加到RunLoop中

  • 实现NSMachPortDelegate方法

  • 创建一个新线程,在新的线程中调用之前创建的NSMachPort对象,往对应的RunLoop发送消息

(2)NSObject 对象相关API:

1
2
3
4

performSelector:onThread:withObject:waitUntilDone:modes:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
......

5、iOS中线程有哪几种状态?

新建、就绪、运行、阻塞、死亡

1
2
3
4
5

At the application level, all threads behave in essentially the same way as on other platforms.
After starting a thread, the thread runs in one of three main states: running, ready, or blocked.
If a thread is not currently running, it is either blocked and waiting for input or it is ready to run but not scheduled to do so yet.
The thread continues moving back and forth among these states until it finally exits and moves to the terminated state.

6、主队列一定在主线程执行吗?

主队列一定在主线程执行,但主线程执行的任务不一定都是主队列的任务。
在SDWebImage中有下面这么一段宏定义,它就是为了保证任务始终在主队列中执行。

1
2
3
4
5
6
7
8
9

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
#endif

7、什么场景下会出现死锁?

同步+串行(主线程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
场景一:同步+主队列

[self task1];
dispatch_sync(dispatch_get_main_queue(), ^{
[self task2];
NSLog(@"同步线程执行主队列任务");
});
[self task3];

(1)执行task1
(2)阻塞同步线程,把task2加入到主队列的队尾
(3)task3需要等待task2执行完成后执行,但是此时task2又排在task3后面,所以造成了死锁

场景二:异步串行队列嵌套同步串行队列

dispatch_queue_t myQueue = dispatch_queue_create("com.bg.sQueue", DISPATCH_QUEUE_SERIAL);
[self task1];
dispatch_async(myQueue, ^{
[self task2];
dispatch_sync(myQueue, ^{
[self task3];
});
[self task4];
});
[self task5];

(1)执行task1
(2)执行task5
(3)执行task2
(4)阻塞同步线程,把task3加入到队列myQueue的队尾
(5)task4需要等待task3执行完成后执行,但是此时task3又排在task4后面,所以造成了死锁

场景三:信号量阻塞主线程

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(sem);
NSLog(@"the sem +1");
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"the sem -1");

主线程中dispatch_semaphore_wait一直等着dispatch_semaphore_signal改变信号量(+1操作),但是dispatch_semaphore_wait却阻塞了主线程导致dispatch_semaphore_signal无法执行,从而造成了死锁。

8、多线程中dispatch_barrier_async为什么不能用全局队列?

dispatch_barrier_async必须用自定义的DISPATCH_QUEUE_CONCURRENT队列,如果使用全局队列或同步队列,则起不到栅栏函数的作用,相当于dispatch_async。

9、这段代码会有什么问题:

1
2
3
4
5
6
7
for (int i = 0; i < 100000; i++) {
NSString *queueN = [NSString stringWithFormat:@"c%dqueue", i];
dispatch_queue_t cQueue = dispatch_queue_create(queueN.UTF8String, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(cQueue, ^{
NSLog(@"the queue = %s", dispatch_queue_get_label(cQueue));
});
}

上述代码会一下子创建很多线程,造成线程爆炸。类似这种多线程循环执行任务场景,可以使用dispatch_apply来实现,因为dispatch_apply的线程全部由GCD管理。从而避免了手动创建线程爆炸问题。

1
2
3
4
5
6
// 解决线程爆炸​方案
dispatch_queue_t queue = dispatch_queue_create("xxQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_apply(100000, queue, ^(size_t t) {
NSLog(@"the current thread = %@, t = %ld", NSThread.currentThread, t);
});

10、GCD的任务能取消吗?

11、自旋锁和互斥锁有什么区别?

12、如何解决多线程安全问题?NSLock是什么锁?

13、如何实现一个常驻线程?(线程保活)

14、如何用GCD实现任务间的依赖?

15、如何使用GCD来实现控制最大并发数?

16、如果需要同时开启3个线程,并按顺序执行任务,使用GCD如何实现?

参考资料:

完整优秀版请移步小专栏:
iOS面试题(多线程篇)

更多好文推荐,扫描下方的二维码,关注《iOS开发秘籍》公众号,免费解锁完整版
在这里插入图片描述

本文内容中部分来自网络,后续会持续更新完善。欢迎一起学习交流!

如需转载,请注明出处

iOS面试题(多线程篇)