iOS面试题(多线程篇)
发布于 2021-09-07 11:53 ,所属分类:2021面试经验技巧分享
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:
performSelector: onThread: withObject:waitUntilDone:modes:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
......
5、iOS中线程有哪几种状态?
新建、就绪、运行、阻塞、死亡
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.
Ifathreadisnotcurrentlyrunning,itiseitherblockedandwaiting
forinputoritisreadytorunbutnotscheduledtodosoyet.
Thethreadcontinuesmovingbackandforthamongthesestatesuntil
itfinallyexitsandmovestotheterminatedstate.
6、主队列一定在主线程执行吗?
主队列一定在主线程执行,但主线程执行的任务不一定都是主队列的任务。
在SDWebImage中有下面这么一段宏定义,它就是为了保证任务始终在主队列中执行。
#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、什么场景下会出现死锁?
同步+串行(主线程)
场景一:同步+主队列
[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、这段代码会有什么问题:
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管理,从而避免了手动创建线程爆炸问题。
//解决线程爆炸方案
dispatch_queue_tqueue=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的任务能取消吗?
(1)iOS8以后,使用dispatch_block_create创建的block任务,可以通过dispatch_block_cancel来取消。
(2)一般我们会直接使用dispatch_async这种函数直接添加任务,面对这种没有使用dispatch_block_create创建block任务的场景,则可以自定义一个全局的BOOL值变量,然后在block中通过该值来判断是否要继续执行当前任务。
11、如何实现一个常驻线程?(线程保活)
可参照 AFNetworking2 的源码(基于NSURLConnection,需要在后台接收并处理代理回调),实现一个常驻线程:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
实现一个常驻线程需要注意以下几个要点:
(1)线程开启后,任务执行完成,线程就销毁了,所以实现常驻线程就需要它一直运行(RunLoop),线程和RunLoop是一一对应的关系,主线程默认开启RunLoop,子线程需要手动开启RunLoop。
(2)RunLoop启动前必须保证有输入源(Source/Observer/Timer),所以这里在执行[runLoop run]方法之前添加了NSMachPort对象,用来保证RunLoop不退出。
(3)[runLoop run]方法会开启一个无限循环来处理输入源的数据,就算从RunLoop中手动移除所有已知的输入源和计时器,仍不能保证RunLoop循环会退出,如果你需要终止RunLoop,你就不应该直接使用 run 方法。不过我们可以直接使用runMode:beforeDate:方法。它只会运行一次循环,可以通过自定义变量来控制是否需要运行或终止。
如 ReactiveObjC 源码所示:
do {
[NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} while (!done);
12、如何解决多线程安全问题?NSLock是什么锁?
多线程安全问题可以通过加锁来解决,下面是常见锁的类型:
互斥锁:@synchronized、NSLock、pthread_mutex实现
递归锁:NSRecursiveLock
信号量:dispatch_semaphore_t
条件锁:NSCondition、NSConditionLock
自旋锁:OSSpinLock:存在优先级反转的问题,目前已经弃用,使用os_unfair_lock(互斥锁)代替
根据objc源码可知 atomic老版本实现是自旋锁(spin_lock_t),新版实现是互斥锁(os_unfair_lock)
如果对性能要求高,可以优先使用信号量。
13、自旋锁和互斥锁有什么区别?
自旋锁,线程会一直处于忙等状态,需消耗CPU资源
互斥锁,线程会挂起处于休眠状态,不会消耗CPU资源
14、如何用GCD实现任务间的依赖?
15、如何用GCD去设计控制最大并发数的实现?
16、如果需要同时开启3个线程,并按顺序执行任务,使用GCD如何实现?
更多答案,请点击“”。
参考资料:
线程编程指引:
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/AboutThreads/AboutThreads.html#//apple_ref/doc/uid/10000057i-CH6-SW2
SDWebImage:https://github.com/SDWebImage/SDWebImage
AFNetworking:https://github.com/AFNetworking/AFNetworking
ReactiveObjC:https://github.com/ReactiveCocoa/ReactiveObjC
相关资源