iOS 多线程面试题

iOS 多线程面试题,第1张

没有比这里更全的了,看我就好了

面试官😃 :你了解进程吗?谈谈你对进程和线程的理解?

不谈进程,线程无从谈起。要了解什么是线程,我们先需要理解什么是线程。

秒懂百科 😃

 以上百科,我们大概了解了进程和线程。也是面试考察的基本点。大致说出画红色的部分也就可以了。

面试官😃 :iOS中,有哪些实现多线程的方式?

这是一道比较综合性的题目。所知道的都回答出来

1,pthread

        特点:C语言。跨平台,可移植,使用难度大。生命周期:自己管理。

2,NSThread

        特点:OC语言。面向对象,简单易用,可直接 *** 作线程。生命周期:自己管理。

3,GCD

        特点:替代NSThread,充分利用多核的技术。生命周期:系统管理。

4,NSOperaton

        特点:基于GCD的封装。比GCD多了一些简单实用的功能。生命周期:系统管理。

面试官😃 :请说一下多线程中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函数。

挖坑了,接下来填坑😃  !面试官😃  可以从以上回答的任意一点展开,深入挖掘面试者的掌握程度。好戏开场了 😃 !

面试官😃 :dispatch_once是怎样保证线程安全的?

我们都知道dispatch_once可以保证线程安全,那么它是怎么做到的呢?

如果你还不能快速手写单例,请多多练习。不能每次碰到单例,就去其他单例类复制吧!

以下只我工作中单例的一种写法,不足欢迎指正:

.h

#import 

NS_ASSUME_NONNULL_BEGIN

@interface PersonTool : NSObject

+ (instancetype)shared;

- (NSString *)getName;


@end

NS_ASSUME_NONNULL_END

.m


#import "PersonTool.h"

@implementation PersonTool

+ (instancetype)shared
{
    static PersonTool *_instace = nil;
    dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instace = [[super allocWithZone:NULL] init];
    });
    return _instace;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    return [PersonTool shared];
}

- (id)copyWithZone:(NSZone *)zone
{
    return [PersonTool shared];
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    return [PersonTool shared];
}

- (NSString *)getName
{
    return @"你好!";
}

dispatch_once的底层实现原理会单开一篇文章!后续补充!!!

面试官😃 :dispatch_after延迟执行,执行时间是准确的吗?

dispatch_after的延迟执行时间不是准确的,因为dispatch_after是在指定时间之后将任务添加到主队列,并不是在指定时间之后开始执行处理!

面试官😃 :说说你对dispatch_apply的理解?

dispatch_apply 是GCD提供的一种快速迭代的函数,按照指定的次数将指定的任务追加到指定的队列中,并等待全部任务结束。

如果用在串行队列,就和for循环一样,按顺序同步执行。

如果用在并发队列,别追加到队列的任务会异步执行,并且等待全部任务结束!

使用实例:

    dispatch_apply(5, dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT), ^(size_t iteration) {
        NSLog(@"iOS");
    });
    
    //会等到上面的任务全部执行结束在执行下面的代码
    NSLog(@"结束");

面试官😃 :说说你对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_group实现异步任务的顺序执行是做不到的!!!

面试官😃 :说说你项目中的哪些功能使用了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"};
}

面试官😃 :请实现一个多读单写的功能?

我们可以用dispatch_barrier_async实现

面试官😃 :说说你对NSOperation/NSOperationQueue的使用和理解?

NSOperation/NSOperationQueue 是系统提供的一套多线程实现方案。实际上NSOperation/NSOperationQueue是基于GCD更高层次的封装,完全面向对象,比GCD简单易用,代码可读性更高。

使用步骤:

1,创建 *** 作,将 *** 作封装到NSOperation对象中,

2,创建队列,NSOperationQueue,

3,将 *** 作添加到队列中,

之后,系统会将队列中的 *** 作取出,在新线程中执行 *** 作。添加到队列中的 *** 作,首先进入准备就绪状态(就绪状态取决于 *** 作之间的依赖关系),然后进入就绪状态的 *** 作开始执行,开始执行的顺序取决于 *** 作之间的相对优先级, *** 作执行结束的顺序,取决于 *** 作本身!

我们先来理解这一部分的 *** 作/队列/线程

* 添加 *** 作到队列,开启新线程!

* 具体开启线程数量,有系统决定! *** 作在哪条线程执行有系统决定!

* 一个队列中同时能并发执行的最大 *** 作数由maxConcurrentOperationCount 最大并发 *** 作数。也就是一个 *** 作队列中的 *** 作是串行还是并发执行,由maxConcurrentOperationCount它决定!

maxConcurrentOperationCount = -1,默认,并发执行,

maxConcurrentOperationCount = 1,串行执行,

maxConcurrentOperationCount = 3,并发执行,一个队列中同时能并发执行的最大 *** 作数是3,但是这个值不应大于系统设定的默认值,

* *** 作的优先级适用于同一队列中的 *** 作,决定了进入准备就绪状态下的 *** 作之间的开始执行顺序,优先级不能取代依赖关系。

* 线程间的通讯 

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
        
}];

* *** 作的状态

op1.isReady; 是否准备就绪

op1.isExecuting; 是否正在执行

op1.isCancelled; 是否已经取消

op1.isFinished; 是否执行完成

* 取消一个 *** 作和队列的取消

 [op1 cancel]; 取消 *** 作,实际上是标记isCancelled状态

 [queue cancelAllOperations]; 取消队列

这里的取消不是真正意义上的取消,而是当当前的 *** 作执行完成之后,不再进行新的 *** 作。

使用NSOperation/NSOperationQueue实现异步任务的顺序执行是做不到的!!!

面试官😃 :你是否在定义过NSOperation?

自定义NSOperation可以通过重写main或者start方法。重写main方法,不需要管理 *** 作的状态属性isExecuting和isFinished。重写start方法需要管理 *** 作的状态属性。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/993461.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存