你了解线程么?谈谈你对进程和线程的理解?
进程是 系统进行资源分配和调度的基本单位
,是操作系统结构的基础,进程是程序的实体。
线程是 独立调度和分派的基本单位
,一条线程是进程中一个单一顺序的控制流。
同一进程中多条线程共享进程中的全部系统资源,一个进程有很多线程,一个进程可以并发多个线程。
iOS中,有哪些实现多线程的方式?
Pthread
特点:C语言。跨平台,可移植,使用难度大。生命周期:自己管理。
NSThread
特点:OC语言。面向对象,简单易用,可直接操作线程。生命周期:自己管理。
GCD
特点:替代 NSThread,充分利用多核的技术。生命周期:系统管理。
NSOperaton
特点:基于GCD的封装。比GCD多了一些简单实用的功能。生命周期:系统管理。
请说一下多线程中 GCD 和 NSOperation 的区别?
GCD:
提供了一次性执行的代码 dispatch_once
,也就是说保证了一段代码在程序执行的过程中只被执行一次,并且是线程安全的!,实现的单例。
提供了 延迟执行
的简便方法 dispatch_after
。
提供了 调度组
的使用,监听一些列异步方法之行结束之后,我们得到统一的通知
- dispatch_group
- dispatch_group_async
- dispatch_group_notify
- dispatch_group_enter/dispatch_group_leave
提供了 快速迭代的方式dispatch_apply
。按照指定的次序将制定的任务追加到指定的队列中,并等待全部队列执行结束!
提供了信号量 dispatch_semaphore_t
,使用信号量可以实现安全的多线程!(加锁的一种方式)
- dispatch_semaphore_wait:信号量减1,阻塞当前线程
- dispatch_semaphore_signal:信号量加1,释放当前线程
提供了栅栏函数 dispatch_barrier_async
,使用栅栏函数 可以实现线程的多读单写
!
NSOpearion:
NSOperatoin是对GCD更高层次的封装
NSOperation可以设置两个操作之间的 依赖关系
。
NSOperation是个抽象类,开发中使用它的两个子类,NSBlockOperation/NSInvocationOperation。
使用KVO,观察NSOperation的 各种状态
(isExecuted是否正在执行,isFinished是否结束,isCancled是否取消)。无法判断GCD的状态。
NSOperation可以设置操作的 优先级
。
NSoperation可以方便的取消一个操作的执行
可以重写NSOperation的main和start函数。
dispatch_once 是怎么保证线程安全的?
定义一个dispatch_once_t的静态变量,标识下面的diapatch_once的block是否执行过了,static修饰会默认将 onceToken其初始化为0
,当值为0时才会 执行block代码块
里面的内容,此时onceToken不为0,当block执行完成,底层会将oneceToken设置为-1
,以后再调用的话不会再走block代码块。
dispatch_after延迟执行,执行时间是准确的吗?
dispatch_after的延迟执行时间不是准确的,因为dispatch_after是在指定时间之后将任务添加到主队列,并不是在指定时间之后开始执行处理!
说说你对 dispatch_apply 的理解?
dispatch_apply
是GCD提供的一种 快速迭代的函数
,按照指定的次数将指定的任务追加到指定的队列中,并等待全部任务结束。
如果用在串行队列,就和for循环一样,按顺序同步执行。
如果用在并发队列,追加到队列的任务会异步执行,并且等待全部任务结束!
说说你对dispatch_group的理解?
GCD提供的队列组,有两种使用方式 dispatch_group_async
和 dispatch_group_enter/dispatch_group_leave
,使用过程中要根据任务类型选择使用哪种方式。
如果任务类型是同步任务:使用 dispatch_group_async
和 dispatch_group_enter/dispatch_group_leave
是同样的,可以实现相同的功能。
如果任务类型是异步任务:比如(AF)网络请求,使用dispatch_group_async不能等到所有异步任务执行完成,就会去执行dispatch_group_notify中的代码,使用dispatch_group_enter/dispatch_group_leave可以实现执行完添加的异步任务,最后执行dispatch_group_notify中的代码
如果你要实现这样一个功能,请求网络A和B,然后根据A/B返回的内容去刷新页面,如果使用dispatch_group,那么只能使用 dispatch_grouo_enter/dispatch_grouo_leave
!!!使用dispatch_group_async是不能实现这个功能的!
说说你项目中的哪些功能使用了dispatch_semaphore_t,解决了什么问题?
使用信号量 dispatch_semaphore_t
可以实现 异步任务的顺序执行
(也就是 将异步任务转换为同步任务执行
)不要阻塞主线程!。也是多线程加锁的一种实现方式,保证线程安全。
dispatch_semaphore_create(intptr_t value) 创建一个队列组,传入得值>=0,传入的值控制控制并发线程的数量!!!,如果我们传入2,那么就表示当前最多有两个线程同时执行。
dispatch_semaphore_signal(dispatch_semaphore_t dsema) 增加信号量,使信号量的值加1!
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 等待当前线程,直到某个时间释放!!!
说说你对NSOperation/NSOperationQueue的使用和理解?
NSOperation/NSOperationQueue 是系统提供的一套多线程实现方案。实际上NSOperation/NSOperationQueue是基于GCD更高层次的封装,完全面向对象,比GCD简单易用,代码可读性更高。
使用步骤:
创建操作,将操作封装到NSOperation对象中,执行的顺序取决于操作之间的相对优先级,操作执行结束的顺序,取决于操作本身!
创建队列 NSOperationQueue,将操作添加到队列中,一个队列中同时能并发执行的最大操作数由maxConcurrentOperationCount 决定,也就是一个操作队列中的操作是串行还是并发执行,由maxConcurrentOperationCount它决定!
- maxConcurrentOperationCount = -1,默认,并发执行
- maxConcurrentOperationCount = 1,串行执行
- maxConcurrentOperationCount = 3,并发执行
系统会将队列中的操作取出,在新线程中执行操作。
操作有几种状态
- op1.isReady; 是否准备就绪
- op1.isExecuting; 是否正在执行
- op1.isCancelled; 是否已经取消
- op1.isFinished; 是否执行完成
取消操作和队列
- [op1 cancel]; 取消操作,实际上是标记isCancelled状态
- [queue cancelAllOperations]; 取消队列
你是否在定义过NSOperation?
自定义NSOperation可以通过重写main或者start方法。重写main方法,不需要管理操作的状态属性isExecuting和isFinished。重写start方法需要管理操作的状态属性。
【面试题】:如果在工作中有这样一个需求,使用AFNetworking请求A接口拿到A接口返回的id_a,用id_a作为参数去请求B接口,拿到B网络返回的name_b去查数据库,然后刷新页面。该怎么实现呢?
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| @property (nonatomic, assign) dispatch_semaphore_t semaphore; @property (nonatomic, assign) dispatch_queue_t queue; - (void)viewDidLoad{ [super viewDidLoad]; //:测试 [self semaphoreSync]; } - (void)semaphoreSync{ // 创建信号量,传入参数0 self.semaphore = dispatch_semaphore_create(0); // 创建队列,这里串行和并发并无区别 self.queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); // 开启一个新线程, // 这里之所以要创建一个新线程,而不是在当前(主线程)执行,是因为,AF的网络请求返回默认是在主线程中执行,如果我们在当前线程执行一下操作,会发生线程死锁的现象, dispatch_async(self.queue, ^{ // 任务A int ida = [self requestA]; // 任务B NSString *name = [self requestB:ida]; // 任务C NSDictionary *res = [self queryDB:name]; NSLog(@"%@", res); dispatch_async(dispatch_get_main_queue(), ^{ // 刷新页面 }); }); } - (int)requestA{ __block int ida = 0; // AF NSArray *paths = @[@(self.currentPage), @(pageNum), @(100)]; [[ZJNetWorkManager shareManager] GetWithUrlString:TC_API(GetCourseList) paths:paths successedBlock:^(BOOL successed, id _Nonnull jsonObject) { ida = 1; // 释放信号量,信号量加1,释放当前线程,然后执行return操作 dispatch_semaphore_signal(self.semaphore); } failedBlock:^(NSError * _Nonnull error) { dispatch_semaphore_signal(self.semaphore); }]; // 信号量减1,阻塞当前线程 dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); return ida; } - (NSString *)requestB:(int)ida{ __block NSString *name; NSArray *paths = @[@(self.currentPage), @(pageNum), @(100), @(ida)]; [[ZJNetWorkManager shareManager] GetWithUrlString:TC_API(GetCourseList) paths:paths successedBlock:^(BOOL successed, id _Nonnull jsonObject) { name = @"你好👋"; dispatch_semaphore_signal(self.semaphore); } failedBlock:^(NSError * _Nonnull error) { dispatch_semaphore_signal(self.semaphore); }]; dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); return name; } - (NSDictionary *)queryDB:(NSString *)name{ //查询数据库,返回结果 return @{@"name":@"name"}; }
|
【面试题】多个网络请求异步完成-执行最后一个?
- dispatch_group_t + dispatch_group_enter() + dispatch_group_leave() + dispatch_group_notify
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
| //资源 NSString *str = @"http://www.jianshu.com/p/6930f335adba"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; // 1.GCD线程组 dispatch_group_t downloadGroup = dispatch_group_create(); for (int i=0; i<10; i++) { dispatch_group_enter(downloadGroup); NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i); dispatch_group_leave(downloadGroup);
}];
[task resume]; } dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ NSLog(@"end"); });
========= 023-03-18 00:00:29.418934+0800 线程等待[9494:456991] 1---1 2023-03-18 00:00:29.434940+0800 线程等待[9494:456990] 7---7 2023-03-18 00:00:29.435649+0800 线程等待[9494:456989] 8---8 2023-03-18 00:00:29.437444+0800 线程等待[9494:456988] 5---5 2023-03-18 00:00:29.437915+0800 线程等待[9494:456988] 9---9 2023-03-18 00:00:29.439086+0800 线程等待[9494:456988] 2---2 2023-03-18 00:00:29.439832+0800 线程等待[9494:456988] 3---3 2023-03-18 00:00:29.447357+0800 线程等待[9494:457208] 4---4 2023-03-18 00:00:29.449366+0800 线程等待[9494:457208] 0---0 2023-03-18 00:00:29.454452+0800 线程等待[9494:456988] 6---6 2023-03-18 00:00:29.454704+0800 线程等待[9494:456826] end
|
【面试题】多个网络请求同步步完成-执行最后一个?
- dispatch_semphore_t + dispatch_semphore_signal(semphore) + dispatch_semphore_wait(semphore)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| NSString *str = @"http://www.jianshu.com/p/6930f335adba"; NSURL *url = [NSURL URLWithString:str]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSURLSession *session = [NSURLSession sharedSession]; // 信号量 < 0 阻塞 dispatch_semaphore_t sem = dispatch_semaphore_create(0); for (int i=0; i<10; i++) { NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%d---%d",i,i); // 信号量 +1 dispatch_semaphore_signal(sem); }]; [task resume]; // 信号量 -1 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"end"); });
|
【面试题】请实现一个多读单写的功能?
我们可以用 dispatch_barrier_async
实现
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
| @interface TKReadWhiteSafeDic() { // 定义一个并发队列 dispatch_queue_t concurrent_queue; // 用户数据中心, 可能多个线程需要数据访问 NSMutableDictionary *userCenterDic; }
@end
// 多读单写模型 @implementation TKReadWhiteSafeDic
- (id)init { self = [super init]; if (self) { // 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列 concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT); // 创建数据容器 userCenterDic = [NSMutableDictionary dictionary]; } return self; }
- (id)objectForKey:(NSString *)key { __block id obj; // 同步读取指定数据 dispatch_sync(concurrent_queue, ^{ obj = [userCenterDic objectForKey:key]; }); return obj; }
- (void)setObject:(id)obj forKey:(NSString *)key { // 异步栅栏调用设置数据 dispatch_barrier_async(concurrent_queue, ^{ [userCenterDic setObject:obj forKey:key]; }); }
|
线程死锁
- 同步主线程死锁
1 2 3 4
| // 1.同步-主线程会造成死锁 :输出结果只有1 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"a"); });
|
造成 死锁
- 异步主线程不死锁
1 2 3 4 5 6 7 8 9 10
| // 2.异步-主线程不会造成死锁: NSLog(@"1"); dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"4"); }); NSLog(@"5"); }); NSLog(@"3");
|
结论:1、3、2、4、5 或 1、2、3、4、5
分析:1、3在主线程先打印;异步开启 子线程
,2 打印;同步不开启 子线程,先打印 4,在打印 5
1 2 3 4 5 6 7 8 9
| NSLog(@"1"); dispatch_sync(dispatch_get_global_queue(0, 0), ^{ NSLog(@"2"); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"4"); }); NSLog(@"5"); }); NSLog(@"3");
|
结论:1、2、5、3、4
分析:1 在主线程先打印;同步不开启 子线程
,2、5 先打印;异步开启 子线程,打印 3,在打印 4
- 串行队列-同步主线程-死锁
1 2 3 4 5 6 7 8 9 10 11 12
| // 3.主线程死锁 // 创建一个串行队列 dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"1"); // 任务1 dispatch_async(queue, ^{ //block1 NSLog(@"2"); // 任务2 dispatch_sync(queue, ^{ //block2 NSLog(@"3"); // 任务3 }); NSLog(@"4"); // 任务4 }); NSLog(@"5"); // 任务5
|
结论:1、5、2
分析:1、5先打印,异步开启子线程 2 打印,之后由于串行队列 3 与 4 相互等待造成 死锁
- 串行-同步-死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // 4.主线程死锁 dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"1"); // 任务1 dispatch_async(queue, ^{ //block1 NSLog(@"2"); // 任务2 dispatch_async(queue, ^{ //block2 NSLog(@"3"); // 任务3 }); dispatch_sync(queue, ^{ //block3 NSLog(@"4"); // 任务3 }); NSLog(@"5"); // 任务4 }); NSLog(@"6"); // 任务5
|
结论:1、6、2
分析:1、6先打印,异步开启子线程 2 打印,又开启子线程由于串行队列 4 与 5 相互等待造成 死锁