多线程问题总结
1.你了解进程吗?谈谈你对进程和线程的理解?
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
以上百科,我们大概了解了进程和线程。也是面试考察的基本点。大致说出画红色的部分也就可以了。
答:
进程:是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是程序的实体。
线程:
- 是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程。
- 线程是独立调度和分配的基本单位。
- 同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符,信号处理等。但同一进程中的多个线程有各自的调用栈,自己的寄存器环境,自己的线程本地存储。
2. iOS中,有哪些实现多线程的方式?
答:
这是一道比较综合性的题目。所知道的都回答出来
- pthread
特点:C语言。跨平台,可移植,使用难度大。生命周期:自己管理。 - NSThread
特点:OC语言。面向对象,简单易用,可直接操作线程。生命周期:自己管理。 - GCD
特点:替代NSThread,充分利用多核的技术。生命周期:系统管理。 - NSOperaton
特点:基于GCD的封装。比GCD多了一些简单实用的功能。生命周期:系统管理。
3.请说一下多线程中GCD和NSOperation的区别?
面试官问这道题,大部分都是从网上找的面试题,想看一下面试者对GCD和NSOperation的掌握程度,知道多少说多少!但是,这不是终结,这只是个开端,慢慢往下看。
口述各自的特点和不同点,从基本谈起:
GCD和NSOperation都是iOS中多线程实现的方式,它们有各自的特点,又有区别的地方。
GCD
1,提供了一次性执行的代码,也就是说保证了一段代码在程序执行的过程中只被执行一次,并且是线程安全的!(dispatch_once
),实现单例。
2,提供了延迟执行的简便方法。(dispatch_after
)
3,提供了调度组的使用,监听一些列异步方法之行结束之后,我们得到统一的通知,(dispatch_group
,dispatch_group_async
,dispatch_group_notify
)(dispatch_group_enter
/dispatch_group_leave
)
4,提供了快速迭代的方式dispatch_apply
。按照指定的次序将制定的任务追加到指定的队列中,并等待全部队列执行结束!
5,提供了信号量(dispatch_semaphore_t)
,使用信号量可以实现安全的多线程!(加锁的一种方式)
6,提供了栅栏函数,dispatch_barrier_async
,使用栅栏函数可以实现线程的多度单写!
NSOpearion
1,NSOperatoin是对GCD更高层次的封装。
2,NSOperation可以设置两个操作之间的依赖关系。
3,NSOperation是个抽象类,开发中使用它的两个子类,NSBlockOperation
/ NSInvocationOperation
。
4,使用KVO,观察NSOperation的各种状态(isExecuted
是否正在执行,isFinished
是否结束,isCancled
是否取消)。无法判断GCD的状态。
5,NSOperation可以设置操作的优先级。
6,NSoperation可以方便的取消一个操作的执行
7,可以重写NSOperation的main
和start
函数。
挖坑了,接下来填坑😃 !面试官😃 可以从以上回答的任意一点展开,深入挖掘面试者的掌握程度。好戏开场了 😃 !
4. dispatch_once是怎样保证线程安全的?
我们都知道dispatch_once
可以保证线程安全,那么它是怎么做到的呢?
如果你还不能快速手写单例,请多多练习。不能每次碰到单例,就去其他单例类复制吧!
以下只我工作中单例的一种写法,不足欢迎指正:
@interface Person5 : NSObject <NSCopying, NSMutableCopying>
+ (instancetype)sharedInstance;
@end
#import "Person5.h"
@implementation Person5
static Person5 *person5 = nil;
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
person5 = [[super allocWithZone:NULL] init];
});
return person5;
}
- (instancetype)copyWithZone:(NSZone *)zone {
return [Person5 sharedInstance];
}
- (instancetype)mutableCopyWithZone:(NSZone *)zone {
return [Person5 sharedInstance];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [Person5 sharedInstance];
}
@end
5. dispatch_after延迟执行,执行时间是准确的吗?
答:
dispatch_after的延迟执行时间不是准确的,因为dispatch_after是在指定时间之后将任务添加到主队列,并不是在指定时间之后开始执行处理!
6. 说说你对dispatch_apply的理解?
答:
dispatch_apply 是GCD提供的一种快速迭代的函数,按照指定的次数将指定的任务追加到指定的队列中,并等待全部任务结束。
- 如果用在
串行队列
,就和for循环一样,按顺序同步执行。 - 如果用在
并发队列
,追加到队列的任务会异步执行,并且等待全部任务结束!
使用实例:
dispatch_apply(5, dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT), ^(size_t iteration) {
NSLog(@"iOS");
});
//会等到上面的任务全部执行结束在执行下面的代码
NSLog(@"结束");
7. 说说你对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_group_enter/dispatch_group_leave!!!使用dispatch_group_async是不能实现这个功能的!
使用dispatch_group实现异步任务的顺序执行是做不到的!!!
方式一:dispatch_async & dispatch_group_enter & dispatch_group_leavel & dispatch_group_wait (已验证,可行)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queueColumn = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueColumn, ^{
[self setrsInfoIsTimer:NO];
});
//...
}
- (void)setrsInfoIsTimer:(BOOL)isTimer
{
//局部变量的group,可以解决dispatch_group_leave(group)奔溃的问题,
dispatch_queue_t serial_queue = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_group_t group = dispatch_group_create();
//MARK: 开始组装数据
dispatch_group_enter(group);
dispatch_async(serial_queue, ^{
NSLog(@"getGuestListIsFirstEnter --- group ---");
[self getHallInfoGroup:group];
});
//阻塞线程SerialQueue1
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_enter(group);
dispatch_async(serial_queue, ^{
NSLog(@"getGuestListIsFirstEnter --- group ---");
[self getGuestListIsFirstEnter:YES isTimer:isTimer group:group];
});
//阻塞线程SerialQueue1
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
//...更新UI
});
//MARK:组装数据结束
}
- (void)getHallInfoGroup:(dispatch_group_t)group
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];
[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {
dispatch_group_leave(group);
} TranFail:^(NSDictionary * _Nonnull errInfo) {
dispatch_group_leave(group);
} showLoading:self.showLoading];
}
-(void)getGuestListIsFirstEnter:(BOOL)firstEnter isTimer:(BOOL)isTimer group:(nullable dispatch_group_t)group
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];
[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {
dispatch_group_leave(group);
} TranFail:^(NSDictionary * _Nonnull errInfo) {
dispatch_group_leave(group);
} showLoading:self.showLoading];
}
代码主要是使用 dispatch_group 来管理多个异步任务的同步问题。
在 SerialQueue2 中异步执行任务,确保每个任务完成后再继续执行下一个任务。
通过 dispatch_group_wait 实现同步行为,确保数据处理顺序不乱,并在所有数据处理完成后回到主线程更新 UI。
方式二:dispatch_group_async & dispatch_group_notify(尚未验证!)
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queueColumn = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
dispatch_async(queueColumn, ^{
[self setrsInfoIsTimer:NO];
});
//...
}
- (void)setrsInfoIsTimer:(BOOL)isTimer
{
//局部变量的group,可以解决dispatch_group_leave(group)奔溃的问题,
dispatch_queue_t serial_queue = dispatch_queue_create("SerialQueue2", DISPATCH_QUEUE_SERIAL);
dispatch_group_t group = dispatch_group_create();
//MARK: 开始组装数据
dispatch_async(serial_queue, group), ^{
[self getHallInfoGroup:group];
});
//阻塞线程SerialQueue1
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_enter(group);
dispatch_async(serial_queue, group), ^{
[self getGuestListIsFirstEnter:YES isTimer:isTimer group:group];
});
//阻塞线程SerialQueue1
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_notify(group, global_queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
//...更新UI
});
});
//MARK:组装数据结束
}
- (void)getHallInfoGroup:(dispatch_group_t)group
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];
[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {
} TranFail:^(NSDictionary * _Nonnull errInfo) {
} showLoading:self.showLoading];
}
-(void)getGuestListIsFirstEnter:(BOOL)firstEnter isTimer:(BOOL)isTimer group:(nullable dispatch_group_t)group
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"",@"XX",@"",@"XX1",@"",@"XX2", nil];
[[KSNetWork shareInstance] postWithOperationType:@"XXX" Params:dict TranSuc:^(NSDictionary * _Nonnull resultInfo) {
} TranFail:^(NSDictionary * _Nonnull errInfo) {
} showLoading:self.showLoading];
}
方式三:请参考 dispatch_semaphore_t
总结:
dispatch_group_async只适用于处理同步任务
dispatch_group_enter / dispatch_group_leave既适用于同步任务,也适用于异步任务
它们是为了处理:等一组异步任务全部结束以后,再执行一个任务的情况
dispatch_group_wait是同步方法,会阻塞线程
dispatch_group_noitify是异步方法,不会阻塞线程
8.说说你项目中的哪些功能使用了dispatch_semaphore_t,解决了什么问题?
答:
这个问题主要考察上面提到的异步任务的顺序执行。
使用信号量(dispatch_semaphore_t)可以实现异步任务的顺序执行(也就是将异步任务转换为同步任务执行)不要阻塞主线程!。也是多线程加锁的一种实现方式,保证线程安全。
dispatch_semaphore_create(intptr_t value)
创建一个队列组,传入得值>=0,传入的值控制控制并发线程的数量!!!,如果我们传入2,那么就表示当前最多有两个线程同时执行。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
等待当前线程,直到某个时间释放!!!
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
增加信号量,使信号量的值加1!!!
如果在工作中有这样一个需求,(使用AFNetworking)请求A接口拿到A接口返回的id_a,用id_a作为参数去请求B接口,拿到B网络返回的name_b去查数据库,然后刷新页面。该怎么实现呢?
当然你可以一层层去嵌套,但是作为有点经验的程序员都会这样干。这时候可以通过信号量(dispatch_semaphore)实现。
以下是主要实现的代码,
@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)];
[[LTXNetWorkManager 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)];
[[LTXNetWorkManager 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"};
}
9.请实现一个多读单写的功能?
答:
我们可以用dispatch_barrier_async实现
10. 说说你对NSOperation/NSOperationQueue的使用和理解?
答:
NSOperation/NSOperationQueue 是系统提供的一套多线程实现方案。实际上NSOperation/NSOperationQueue是基于GCD更高层次的封装,完全面向对象,比GCD简单易用,代码可读性更高。
使用步骤:
1,创建操作,将操作封装到NSOperation对象中,
2,创建队列,NSOperationQueue,
3,将操作添加到队列中,
这句话很重要:
之后,系统会将队列中的操作取出,在新线程中执行操作。添加到队列中的操作,首先进入准备就绪状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作开始执行,开始执行的顺序取决于操作之间的相对优先级,操作执行结束的顺序,取决于操作本身!
我们先来理解这一部分的操作/队列/线程
- 添加操作到队列,开启新线程!
- 具体开启线程数量,有系统决定!操作在哪条线程执行有系统决定!
- 一个队列中同时能并发执行的最大操作数由
maxConcurrentOperationCount
最大并发操作数。也就是一个操作队列中的操作是串行还是并发执行,由maxConcurrentOperationCount
它决定!
maxConcurrentOperationCount = -1,默认,并发执行,
maxConcurrentOperationCount = 1,串行执行,
maxConcurrentOperationCount = 3,并发执行,一个队列中同时能并发执行的最大操作数是3,但是这个值不应大于系统设定的默认值, - 操作的优先级适用于同一队列中的操作,决定了进入准备就绪状态下的操作之间的开始执行顺序,优先级不能取代依赖关系。
- 线程间的通讯
- 操作的状态
op1.isReady; 是否准备就绪
op1.isExecuting; 是否正在执行
op1.isCancelled; 是否已经取消
op1.isFinished; 是否执行完成 - 取消一个操作和队列的取消
[op1 cancel]; 取消操作,实际上是标记isCancelled状态
[queue cancelAllOperations]; 取消队列
这里的取消不是真正意义上的取消,而是当当前的操作执行完成之后,不再进行新的操作。
使用NSOperation/NSOperationQueue实现异步任务的顺序执行是做不到的!!!
11你是否在定义过NSOperation?
答:
自定义NSOperation可以通过重写main或者start方法。重写main方法,不需要管理操作的状态属性isExecuting和isFinished。重写start方法需要管理操作的状态属性。