美文网首页
iOS 多线程

iOS 多线程

作者: yyggzc521 | 来源:发表于2019-01-10 20:32 被阅读0次
    进程与线程区别

    A8处理器是一款双核处理器,A12处理器是6核CPU
    进程是cpu资源分配的最小单位,线程是cpu调度的最小单位
    一个程序至少有一个进程,一个进程至少有一个线程。线程依赖于进程才能运行
    多线程实现主要是靠硬件CPU(中央处理器)件来实现的,CPU有一个很重要的特性时间片

    • 缺点

    更多的线程意味着更多的内存开销。创建线程也是需要CPU开销的
    如果线程比核的数量多,则同一时间只能执行与核数量相等的线程数,线程过多会导致频繁的切换,消耗过多的CPU时间,降低了程序性能
    多线程就可能出现线程安全问题,为了解决线程安全需要使用锁,进而可能会出现死锁问题。过多的线程会增加程序设计的复杂性,浪费更多精力去处理多线程通信和数据共享

    多线程方案.png 队列和同步、异步的组合结果.png

    总结:只要是同步或者是主队列都不会创建子线程!!!

    NSThread

    使用NSThread需要自己管理线程生命周期

    类方法:自动开启

    [NSThread detachNewThreadSelector:@selector(timerThread) toTarget:self withObject:nil];
    

    对象方法:需要手动开启

    _eocThread = [[NSThread alloc] initWithTarget:self selector:@selector(timerThreadTwo) object:nil];
        [_eocThread start];
    
    self.block = ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            #//线程内部调用这个代码 会使线程睡眠 暂停1秒
            [NSThread sleepForTimeInterval:1];
            NSLog(@"%@",weakSelf);
        });
    };
    

    GCD

    优点

    1. 多核并行运算
    2. 自动管理生命周期
    3. 简单高效

    创建队列

        dispatch_queue_create(@"标识", DISPATCH_QUEUE_SERIAL)//串行
        dispatch_queue_create(@"标识", DISPATCH_QUEUE_CONCURRENT)//并行
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0)//获取全局并发队列
        dispatch_queue_t queue = dispatch_get_main_queue()//主队列
    
    注意:queue4和queue5并不是同一个对象,最好不要这么写

    NSOperation

    基于GCD,更加面向对象,但是处理简单任务会比GCD代码更多 ;
    需要配合 NSOperationQueue 来实现多线程。因为默认情况下,NSOperation 单独使用时系统同步执行操作
    NSOperation中所有的任务执行完finished == YES,并且任务的执行不是按照先进先出执行的,而是并发无序的执行!!!

    优点

    1. 添加操作之间的依赖关系,方便的控制执行顺序
    2. 设定操作执行的优先级
    3. 可以很方便的取消一个操作的执行
    4. 使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled

    NSOperationQueue

    • 主队列: [NSOperationQueue mainQueue] 和GCD中的主队列一样,串行队列
    • [[NSOperationQueue alloc] init] 非常特殊(同时具备并发和串行的功能) 默认情况下,非主队列是并发队列
    • 操作队列通过设置 最大并发操作数(maxConcurrentOperationCount) 来控制并发、串行
    • 不同于 GCD 中的调度队列 FIFO(先进先出)的原则。由操作之间相对的优先级决定
    1. NSBlockOperation
    operationQueue = [NSOperationQueue new];
        blockOperation = [NSBlockOperation new];
        
        [blockOperation addExecutionBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
            sleep(1);
            NSLog(@"one finish");
        }];
        
        [blockOperation addExecutionBlock:^{
           NSLog(@"%@", [NSThread currentThread]);
            sleep(1);
            NSLog(@"two finish");
        }];
        operationQueue.maxConcurrentOperationCount = 4;//设置线程最大并发数
        [operationQueue addOperation:blockOperation];
    

    任务执行完毕后再次调用start方法,operation并不会再次执行,因为内部进行判断,当finish == YES 时不再执行

    [blockOperation start];
    

    自定义封装NSOperation

    NSOperation只需要重写其中的main或start方法,在多线程执行任务的过程中需要注意线程安全问题,
    我们还可以通过KVO监听isCancelled、isExecuting、isFinished等属性,确切的回调当前任务的状态

    • 我们同时重写start和main方法时,start方法优先执行,main方法不会被执行;如果只重写main方法,则main方法会被执行。
    • 因为isFinished是readonly属性,因此我们通过自定义变量taskFinished来重写isFinished的set、get方法,实现方式详见代码。
    1. NSOperationQueue的maxConcurrentOperationCount一般设置在5个以内,数量过多可能会有性能问题。maxConcurrentOperationCount为1时,队列中的任务串行执行,maxConcurrentOperationCount大于1时,队列中的任务并发执行;
    1. 不同的NSOperation实例之间可以设置依赖关系,不同queue的NSOperation之间也可以创建依赖关系 ,但是要注意不要“循环依赖”;
    2. NSOperation实例之间设置依赖关系应该在加入队列之前;
    3. 在没有使用 NSOperationQueue时,在主线程中单独使用 NSBlockOperation 执行(start)一个操作的情况下,操作是在当前线程执行的,并没有开启新线程,在其他线程中也一样;
    4. NSOperationQueue可以直接获取mainQueue,更新界面UI应该在mainQueue中进行;
      区别自定义封装NSOperation时,重写main或start方法的不同;
      自定义封装NSOperation时需要我们完全重载start,在start方法里面,我们还要查看isCanceled属性,确保start一个operation前,task是没有被取消的。如果我们自定义了dependency,我们还需要发送isReady的KVO通知。
    死锁产生的条件
    1. sync
    2. 往当前串行队列添加任务


      死锁.png
      子线程保活.png
    这种队列组只能在同步任务的情况下使用.png

    队列组使用注意事项

    线程同步

    锁的对象必须是唯一的同一个,原理:会判断这个操作有没有被别的线程加锁,如果有就一直等待,否则就加锁

    • OSSpinLock自旋锁


      自旋锁.png

    优先级反转就是低优先级的线程加锁之后,高优先级的线程一直处于忙等状态,系统把所有的时间都分给了高优先级的线程,所以没有时间处理低优先级的任务,造成低优先级的一直无法完成,高优先级的线程一直忙等
    自旋锁的效率还是很高的,不过iOS10之后Apple不推荐使用,而是推荐os_unfair_lock

    • os_unfair_lock 也是一种互斥锁


      os_unfair_lock.png
    • pthread_mutex(互斥锁)


      pthread_mutex.png
    • 递归锁 允许 同一个线程对一把锁重复加锁
      递归锁.png
    • 条件锁 条件锁.png
    • 信号量


      信号量.png
    @interface SemaphoreDemo()
    
    @property (strong, nonatomic) dispatch_semaphore_t semaphore;
    @end
    
    @implementation SemaphoreDemo
    //初始化
    self.moneySemaphore = dispatch_semaphore_create(1);
    //forever一直等待 信号量--1
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    //信号量++1
    dispatch_semaphore_signal(self.moneySemaphore);
    
    @end
    

    dispatch_semaphore_wait的声明为:

    long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    

    这个函数会使传入的信号量dsema的值减1;
    这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
    如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,
    不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
    如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

    //配置域名节点,因为项目的域名地址都要依赖这个,所以加一个信号量确保配置完成再往下走  
        dispatch_semaphore_t semap = dispatch_semaphore_create(0);
        [[SFIMLocationDomainNodeManager sharedInstance] locationDomainNodeFinish:^(BOOL locationSuccess) {
            if (!locationSuccess) {
                DDLogInfo(@"连接所有域名节点都失败了,默认选择内地节点");
            }
            dispatch_semaphore_signal(semap);
        }];
        dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
        dispatch_semaphore_wait(semap, t);
    

    NSLock、NSRecursiveLock、NSCondition、NSConditionLock、串行队列

    参考
    多线程之NSOperation
    https://juejin.im/post/5c14bf1af265da613f2f5e90
    https://juejin.im/post/5bf21d935188251d9e0c2937(多线程锁)
    https://juejin.im/post/5a9e57af6fb9a028df222555 这个详细
    https://www.cnblogs.com/yajunLi/p/6274282.html 信号量的使用
    dispatch_group

    相关文章

      网友评论

          本文标题:iOS 多线程

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