• 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 有如下几种的运行状态:状态控制
    * Pending
    * 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;
    }
    @synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改,保证代码的安全性。也就是包装这段代码是原子性的,安全的。
    这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其他线程访问,起到保护线程安全的作用


    1、

    atomic:变量默认是有该有属性的,这个属性是为了保证在多线程的情况下,编译器会自动生成一些互斥加锁的代码,避免该变量的读写不同步的问题。  
    nonatomic:如果该对象无需考虑多线程的情况,这个属性会让编译器少生成一些互斥代码,可以提高效率。

    2、使用GCD实现atomic操作:给某字段的setter和getter方法加上同步队列

    
3、使用NSLock


    4、使用互斥锁
    ————————————————————————————————————————————————

    //互斥锁 -- 保证锁内的代码在同一时间内只有一个线程在执行
            @synchronized (self){}

    //使用NSLock

         NSLock* myLock=[[NSLock alloc]init];
        NSString *str=@"hello";
        [NSThread detachNewThreadWithBlock:^{
            [myLock lock];
            NSLog(@"%@",str);
            str=@"world";
            [myLock unlock];
         }];
        [NSThread detachNewThreadWithBlock:^{
            [myLock lock];
            NSLog(@"%@",str);
            str=@"变化了";
            [myLock unlock];
        }];

    输出结果不加锁之前,两个线程输出一样 hello;加锁之后,输出分别为hello 与world。


    “自旋锁OSSpinLock” & “互斥锁”的异同:
    * 共同点
都能够保证线程安全
    * 不同点


        互斥锁:如果其他线程正在执行锁定的代码,此线程就会进入休眠状态,等待锁打开;然后被唤醒


        自旋锁:如果线程被锁在外面,那么就会用死循环的方式一直等待锁打开!
        自旋锁OSSpinLock用于轻量级的数据计算,如int型的+1、-1,在内存管理里面,对引用计数器的加减就使用了自旋锁。


    递归锁NSRecursiveLock,可以重入,不会造成死锁。
    NSLock不可以重入,会造成死锁。

    换成递归锁就不会导致死锁了。


    线程间通信
    对于线程间通信常见的是线程间同步控制,比如通过线程锁、GCD队列、NSOperationQueue操作队列;
    若涉及到线程间同步传递数据,最有效的方式是通过共享的进程内存并结合线程锁来控制数据同步;
    若涉及到线程间异步传递数据,可通过mach port或者performSelector:onThread:withObject:waitUtilDone:并结合runloop来实现。
    传递数据大小而言,对于大容量数据,一般会存储到临时文件并传递文件描述符;
    具体的线程间通信方式需要根据实际的使用场景来选择,如同步/异步、传递数据大小、单向/双向通信等。

    performSelector实现线程间通信(实现子线程和主线程互相通信,前提是子线程需要保活。【https://www.jianshu.com/p/eae43bdb7eb8】)
    NSPort实例 实现线程间通信

    案列:图片下载的案列,在子线程下载图片,在主线程更新UI

    其他:

    PerformSelector 的实现原理?
    
    当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
    
    当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
  • 相关阅读:
    转载: SQLyog连接MySQL8 异常2059Authentication plugin 'caching_sha2_password' cannot be loaded解决方案
    linux 运维知识主页
    解决Xshell连接远程linux服务器,关闭Xshell程序对应的运行程序也相
    WPF布局控件
    WPF属性
    AutoMapper 四啥注意
    引用类型和值类型
    蠢鸟之xamarin.forms下面加载字体之二
    一头好奇的猫(庆军)之AES ECB模式C#要跟JAVA一致的话需要注意的
    加班
  • 原文地址:https://www.cnblogs.com/somethingWithiOS/p/16075547.html
Copyright © 2020-2023  润新知