iOS面试之多线程大全

多线程

多线程内容如下:

  • GCD
  • NSOperation
  • NSThread
  • 多线程与锁
image

1.GCD

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:413038000,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

推荐阅读

iOS开发——最新 BAT面试题合集(持续更新中)

- 同步/异步 和 串行/并发
- dispatch_barrier_async
- dispatch_group

  • 同步/异步和串行/并发
//同步分派一个任务到串行队列
- dispatch_sync(serial_queue,^{//任务});
//异步废牌一个任务到串行队列
- dispatch_async(serial_queue,^{//任务});
//同步分派一个任务到并发队列
- dispatch_sync(concurrent_queue,^{//任务})
//异步分派一个任务到并发队列
- dispatch_async(concurrent_queue,^{//任务})

  • 同步串行
-(void)viewDidload{
     dispatch_sync(dispatch_get_main_queue(),^{
         [self doSomething];
     });
}
//产生死锁
原因:队列引起的循环等待

image
- 主队类先提交一个viewDidLoad,接着提交一个Block
- 两个任务都要提交到主线程去执行
- 我们分派viewDidLoad到主线程处理,需要调用Block,当Block同步调用完成之后,
viewDidLoad方法才可以继续向下执行
- viewDidLoad方法的调用结束,需要依赖于后续提交的Block任务
- Block想执行,需要依赖主队列先进先出的性质,需要等待ViewDidLoad的完成
- Block想要处理,依赖于ViewDidLoad的完成
- 所以,形成了相互等待

-(void)viewDidload{
     dispatch_sync(serialQueue,^{
         [self doSomething];
     });
}
//没问题

image
- viewDidLoad方法提交到主队列当中,会运行处理到主线程中
- viewDidLoad执行到某一时刻,需要同步提交一个任务到串行队列
- 串行队列其实是同步方式提交的,在当前线程执行,最终在主线程执行
- 串行队列任务,在主线程提交完成后,再到主队类完成viewdidload其他任务

  • 同步并发
-(void)viewDidLoad{
           NSLog(@"1");
           dispatch_sync(global_queue,^{
                 NSLog(@"2");
                 dispatch_sync(global_queue,^{
                         NSLog(@"3");
                 });
                 NSLog(@"4");
           });
           NSLog(@"5");
}

//12345
//同步分派任务在当前线程中执行

  • 异步串行
-(void)viewDidload{
     dispatch_async(dispatch_get_main_queue(),^{
         [self doSomething];
     });
}

  • 异步并发
-(void)viewDidload{
     dispatch_async(global_queue,^{
         NSLog(@"1");
         [self performSelector:@selector(logPrint)] withObject: nil afterDelay:0];
         NSLog(@"3");
     });
}
-(void)logPrint{
      NSLog(@"2");
}

//13
- 我们通过异步方式分派到全局并发队列中,本身block,会在GCD维护的线程池当中进行执行,处理.GCD默认并没有开启runloop
- performSelector: withObject: afterDelay: 延迟0秒提交Selector任务,需要相应创建提交runloop任务的逻辑的
- GCD创建的线程没有runloop的情况下, performSelector方法就会失效

  • dispathc_barrier_async()
    如何使用GCD实现多读单写?
image
- 读者,读者并发(实现多读)
- 读者,写着互斥(有读取数据的时候,不能存在写者写数据)
- 写着,写着互斥(有一个写线程在写数据,另一个写线程就不能写数据)

image
dispathc_barrier_async(concurrent_queue,^{//写操作});

#import "User.h"

@interface User()
{
    // 定义一个并发队列
    dispatch_queue_t concurrent_queue;
    // 用户数据中心, 可能多个线程需要数据访问
    NSMutableDictionary *userDic;
}

@end
// 多读单写模型
@implementation User

- (id)init
{
    self = [super init];
    if (self) {
        // 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
        concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 创建数据容器
        userDic = [NSMutableDictionary dictionary];
    }

    return self;
}

- (id)objectForKey:(NSString *)key
{
    __block id obj;
    // 同步读取指定数据
    dispatch_sync(concurrent_queue, ^{
        obj = [userDic objectForKey:key];
    });
    return obj;
}

- (void)setObject:(id)obj forKey:(NSString *)key
{
    // 异步调用设置数据
    dispatch_barrier_async(concurrent_queue, ^{
        [userDic setObject:obj forKey:key];
    });
}

  • Dispatch_group_async()
使用GCD实现:A,B,C三个任务并发,完成后执行任务D

image
#import "Group.h"

@interface Group()
{
    dispatch_queue_t concurrent_queue;
    NSMutableArray <NSURL *> *arrayURLs;
}

@end

@implementation Group

- (id)init
{
    self = [super init];
    if (self) {
        // 创建并发队列
        concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
        arrayURLs = [NSMutableArray array];
    }
    return self;
}

- (void)handle
{
    // 创建一个group
    dispatch_group_t group = dispatch_group_create();

    // for循环遍历各个元素执行操作
    for (NSURL *url in arrayURLs) {
        // 异步组分派到并发队列当中
        dispatch_group_async(group, concurrent_queue, ^{
            //根据url去下载图片
            NSLog(@"url is %@", url);
        });
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 当添加到组中的所有任务执行完成之后会调用该Block
        NSLog(@"全部下载完成");
    });
}
@end

2.NSOperation

需要与NSOperationQueue配合使用来实现多线程方案

优势特点:
- 添加任务依赖
- 任务执行状态控制
- 最大并发量

  • 任务执行状态控制
状态有哪些?
- isReady 当前任务的就绪状态
- isExecuting 当前任务是否正在执行中
- isFinished 当前任务是否已经执行完成
- isCancelled 当前任务是否已经取消

状态控制
如果只重写main方法,底层控制变更任务执行完成状态,以及任务退出
如果重写了start方法,自行控制任务状态

系统是怎样移除一个isFinished=YES的NSOperation的?
通过KVO

3.NSThread

image
start()
|
main()
|
perfomSelector
|
exit(结束线程)

4.多线程与锁

在日常开发中,都使用过哪些锁?
- @synchronized
- atomic
- OSSpinLock
- NSRecursiveLock
- NSLock
- dispatch_semaphore_t

  • @synchronized
- 一般在创建单例对象的时候使用
- 多线程环境下创建线程是唯一的

  • atomic
- 修饰属性的关键字
- 对被修饰对象进行原子操作(不负责使用)

@property(atomic) NSMutableArray *array;
self.array = [NSMutableArray array];//这样保证线程的安全性
[self.array addObject:obj];//不能保证线程安全的

  • OSSpinLock
- 自旋锁
- 循环等待访问,不释放当前资源
- 用于轻量级数据访问(引用计数+1/-1操作)

  • NSLock
-(void)A{
    [lock lock];
    [self B];
    [lock unlock];
}

-(void)B{
   [lock lock];
   //操作逻辑
   [lock unlock];
}
//导致死锁
- 使用NSLock 对临界区加锁处理的时候,当前某个线程调用lock之后,获取得到锁
- 到B方法后,同一把锁友获取了一次,导致了死锁

//解决方案
通过递归锁NSRecursiveLock

  • NSRecursiveLock
-(void)A{
    [recursiveLock lock];
    [self B];
    [recursiveLock unlock];
}

-(void)B{
   [recursiveLock lock];
   //操作逻辑
   [recursiveLock unlock];
}

递归锁的特点就是重入

  • dispatch_semaphore_t
- 信号量
- dispatch_semaphore_create(1);
- dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
- dispatch_semaphore_signal(semaphore);

面试题:

  • 怎样用GCD实现多读单写?
  • iOS系统为我们提供几种多线程技术各自特点是怎样的?
  • NSOperation对象在isFinished之后是怎样从queue当中移除掉的?
  • 用过哪些锁?你是怎样使用的?

文章来源于网络,如有侵权,请联系小编删除。

推荐阅读更多精彩内容

  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 261评论 0 2
  • 一、前言 上一篇文章iOS多线程浅汇-原理篇中整理了一些有关多线程的基本概念。本篇博文介绍的是iOS中常用的几个多...
    nuclear阅读 1,245评论 6 18
  • NSThread NSThread是封装程度最小最轻量级的,使用更灵活,但要手动管理线程的生命周期、线程同步和线程...
    泥孩儿0107阅读 218评论 0 1
  • 在这篇文章中,我将为你整理一下 iOS 开发中几种多线程方案,以及其使用方法和注意事项。当然也会给出几种多线程的案...
    张战威ican阅读 201评论 0 0
  • 主队列 细心的同学就会发现,每套多线程方案都会有一个主线程(当然啦,说的是iOS中,像 pthread 这种多系统...
    京北磊哥阅读 190评论 0 0