2022iOS面试题之多线程

GCD特点:
1、GCD是基于c语言的用于多核的并行运算
2、GCD会自动利用更多的CPU内核(比如双核、四核)
3、GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
4、程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

队列串行队列:会顺序执行
         并行队列:可以并行执行
         全局队列:系统创建,全局并发队列
         主队列:主队列与主线程是绑定的

只要是同步的方式提交任务,无论是串行还是并发,就会在同一线程去执行。
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新线程中执行任务,具备开启新线程的能力
———————————————————————————————
死锁

原因:主队列里sync 同步执行,就是会先阻塞当前线程,直到block 当中的代码执行完毕,但是block在viewdidload里面,block需要等待viewdidload执行完结束才能继续,但是viewdidload需要等待block执行完才能结束。
可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。

- (void)viewDidLoad {    [super viewDidLoad];   //会死锁,都在主队列里同步执行    dispatch_sync(dispatch_get_main_queue(), ^{        NSLog(@"deallock");    });}解决- (void)viewDidLoad {    [super viewDidLoad];    //不会死锁,serialQueue虽然是串行队列,但不是主队列,serialQueue和viewDidLoad(在主队列执行)的执行队列不同就不会等待,    dispatch_sync(serialQueue, ^{        NSLog(@"deallock");    });}


———————————————————————————————
面试题:多读单写的GCD实现?异步栅栏到并发队列方案[就是像筑起一个栅栏一样,将队列中的多组线程任务分割开]

-(id)objectForKey: (NSString *) key{  __block id obi:
  dispatch_queue_t concurrent_queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);   
//同步读取指定数据,单读  dispatch_sync(concurrent_queue, ^{    obj= [userCenterDic obiectForKey: key];
  });
  return obi;
}-(
void)setobject: (id)obj forKey: (NSString *) key{
  dispatch_queue_t concurrent_queue = dispatch_queue_create("barrier", DISPATCH_QUEUE_CONCURRENT);
 //异步栅栏调用设置数据,多写   
  dispatch_barrier_async (concurrent_queue,
^{  
   [userCenterDic setobiect:obi forKey: key];   
  });
}
区别?
1、dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务(4、5、6),然后执行后面的任务
2、dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(4、5、6)插入到queue,然后执行任务。

注意:在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用。

—————————————————————————————————
面试题:A/B/C任务并发执行完成后,再调用D任务?
在n个耗时并发任务都完成后,再去执行接下来的任务。比如,在n个网络请求完成后去刷新UI页面。
使用dispatch_group 队列组。

dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);     dispatch_group_t group = dispatch_group_create();        for (NSInteger i = 0; i < 10; i++) {                dispatch_group_async(group, concurrentQueue, ^{                        sleep(1);                        NSLog(@"%zd:网络请求",i);        });    }        dispatch_group_notify(group, dispatch_get_main_queue(), ^{                NSLog(@"刷新页面");    });

——————————————————————————————
使用信号量,控制多线程下的同步操作。(作用:保证关键代码段不被并发调用。)

  1. 初始化信号(initialize/create)  2. 发信号(signal/post)  3. 等信号(wait/suspend)  4. 释放信号(destroy)    dispatch_semaphore_t sem = dispatch_semaphore_create(0);    dispatch_async(dispatch_get_global_queue(0, 0), ^{                NSLog(@"任务1:%@",[NSThread currentThread]);        dispatch_semaphore_signal(sem);    });        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);        dispatch_async(dispatch_get_global_queue(0, 0), ^{        NSLog(@"任务2:%@",[NSThread currentThread]);        dispatch_semaphore_signal(sem);    });        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);        dispatch_async(dispatch_get_global_queue(0, 0), ^{        NSLog(@"任务3:%@",[NSThread currentThread]);    });


———————————————————NSOperation——————————————

特性:

1.Dependency 设置依赖,优先级2.NSOperation 有如下几种的运行状态:状态控制* Pendi 河北干部培训 www.gsganxun.cn ng* Ready* Executing* Finished* Canceled除 Finished 状态外,其他状态均可转换为 Canceled 状态。3. maxConcurrentOperationCount,默认的最大并发 operation 数量是由系统当前的运行情况决定的(来源),我们也可以强制指定一个固定的并发数量.
4、可以很方便的取消一个操作的执行。
5、使用KVO观察对操作执行状态的更改:isExecuting、isFinished、isCancelled。

NSOperation的任务状态

isReady 当前任务是否处于就绪状态isExecuting 当前任务是否处于正在执行中状态isFinished 当前任务是否已执行完毕isCancelled 当前任务是否已取消


怎么去控制状态?重写main方法的话,底层帮我们自动控制的。重写start方法才是我们控制状态

1.如果只重写了NSOperation的main方法,底层会为我们控制变更任务执行完成状态,以及任务退出(后续线程的退出和NSOperation的退出)2.如果重写了NSOperation的start方法,需要我们自行控制任务状态,在合适的时机去修改对应的isFinished等     查看NSOperation的start方法源码,理解上面两点    start方法内,首先创造一个自动释放池,然后获取线程优先级    做一系列的状态异常判断,然后判断当前状态是否isExecuting    如果不是,那么我们手动变成isExecuting,然后判断当前任务是否有被取消    若未被取消就调用NSOperation的main方法    再之后,调用NSOperation的finish方法。finish 方法中:在内部通过KVO的方式去变更isExecuting状态为isFinished状态    之后调用自动释放池的release 所以系统是在start方法里面为我们维护了任务状态的变更,若重写start,则没人帮我们维护了,只能自己手动维护

kvo监听状态属性的变化,来确定是否需要移除的,通过 KVO 监测 isExecuting 和 isFinished 这几个变量,来监测 Operation 的完成状态的

//设置优先级最高    op1.qualityOfService = NSQualityOfServiceUserInteractive; //设置依赖 : 列如:下载 解压  升级完成    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{            NSLog(@"下载");    }];    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{        [NSThread sleepForTimeInterval:2.0];        NSLog(@"解压");    }];    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{             NSLog(@"升级完成");    }];    //设置操作间的依赖    [op2 addDependency:op1];    [op3 addDependency:op2];    //会发生循环依赖  ,什么都不操作    //操作添加到队列中    [self.queue addOperations:@[op1,op2] waitUntilFinished:NO];    //依赖关系可以跨队列    [[NSOperationQueue mainQueue] addOperation:op3];


GCD 与 NSOperation 的对比

* 首先要明确一点,NSOperationQueue 是基于 GCD 的更高层的封装.
* 从易用性角度,GCD 由于采用 C 风格的 API,在调用上比使用面向对象风格的 NSOperation 要简单一些。
* 从对任务的控制性来说,NSOperation 显著得好于 GCD,和 GCD 相比支持了 Cancel 操作(注:在 iOS8 中 GCD 引入了 dispatch_block_cancel 和 dispatch_block_testcancel,也可以支持 Cancel 操作了),支持任务之间的依赖关系,支持同一个队列中任务的优先级设置,同时还可以通过 KVO 来监控任务的执行情况。这些通过 GCD 也可以实现,不过需要很多代码,使用 NSOperation 显得方便了很多。
* 效率,直接使用 GCD 效率确实会更高效,NSOperation 会多一点开销,但是通过 NSOperation 可以获得依赖,优先级,继承,键值对观察这些优势
* 从第三方库的角度,知名的第三方库如 AFNetworking 和 SDWebImage 背后都是使用 NSOperation,也从另一方面说明对于需要复杂并发控制的需求,NSOperation 是更好的选择
————————————————————————————————————————————————
线程间通信:切换到主线程[NSOperationQueue mainQueue]

———————————————————NSThread———————————————————————————
NSThread在实际开发中比较常用到的场景就是去实现常驻线程。
将耗时操作放在子线程中,并且在子线程中开启runloop,并使子线程常驻,这样就能不停的执行耗时操作,并且不会影响到主线程啦,滑动tableview很丝滑

   NSThread *thread = [[NSThread alloc] initWithBlock:^{    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.35 repeats:YES block:^(NSTimer * _Nonnull timer) {        NSLog(@"1234567"); static int count = 0; [NSThread sleepForTimeInterval:1]; //休息一秒钟,模拟耗时操作 }];; [self.timer fire]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run];}];[thread start];

如何保证线程安全?
NSOperation的线程安全
和其他多线程方案一样,解决NSOperation多线程安全问题,可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。iOS 实现线程加锁有很多种方式。

@synchronized、NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock等等各种方式。


@synchronized一般在创建单例对象的时候使用,保证创建的对象是唯一的,但是性能没有dispatch_once_t好。

@implementation XXClass//@synchronized来实现+ (id)sharedInstance {    static XXClass *sharedInstance = nil;    @synchronized(self) {        if (!sharedInstance) {            sharedInstance = [[self alloc] init];        }    }    return sharedInstance;}//dispatch_once_t来实现@implementation XXClass+ (id)sharedInstance {    static XXClass *sharedInstance = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        sharedInstance = [[self alloc] init];    });    return sharedInstance;}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值