iOS面试之多线程模块

作者: 木子心语 | 来源:发表于2019-11-26 00:00 被阅读0次

    多线程

    多线程内容如下:

    • GCD
    • NSOperation
    • NSThread
    • 多线程与锁
    多线程.png

    1.GCD

    - 同步/异步 和 串行/并发
    - 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];
         });
    }
    //产生死锁
    原因:队列引起的循环等待
    
    队列引起的循环等待.png
    - 主队类先提交一个viewDidLoad,接着提交一个Block
    - 两个任务都要提交到主线程去执行
    - 我们分派viewDidLoad到主线程处理,需要调用Block,当Block同步调用完成之后,
    viewDidLoad方法才可以继续向下执行
    - viewDidLoad方法的调用结束,需要依赖于后续提交的Block任务
    - Block想执行,需要依赖主队列先进先出的性质,需要等待ViewDidLoad的完成
    - Block想要处理,依赖于ViewDidLoad的完成
    - 所以,形成了相互等待
    
    -(void)viewDidload{
         dispatch_sync(serialQueue,^{
             [self doSomething];
         });
    }
    //没问题
    
    同步串行.png
    - 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实现多读单写?
    读写数据.png
    - 读者,读者并发(实现多读)
    - 读者,写着互斥(有读取数据的时候,不能存在写者写数据)
    - 写着,写着互斥(有一个写线程在写数据,另一个写线程就不能写数据)
    
    多读单写.png
    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
    
    Dispatch_group_async.png
    #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

    NSThread启动流程.png
    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当中移除掉的?
    • 用过哪些锁?你是怎样使用的?

    QQ交流群: 796142709

    相关文章

      网友评论

        本文标题:iOS面试之多线程模块

        本文链接:https://www.haomeiwen.com/subject/qiihwctx.html