iOS晋级知识汇总(十)多线程

多线程

  • GCD
  • NSOperation(AFnetwork可以查看)
  • NSThread
  • 多线程与锁

GCD的相关问题

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

同步?异步?串行?并发

  • dispatch_sync(serial_queue,^{});
  • dispatch_async(serial_queue,^{});
  • dispatch_sync(concurrent_queue,^{});
  • dispatch_async(concurrent_queue,^{});

在主线程中开启一个GCD同步任务

  • 队列引起的循环等待,造成的死锁
    • 在主线程中开启了一个同步任务,而这个同步任务,需要你前面的任务完成,而主线程在iOS中是一个特殊的线程,它会一直存在于你的程序结束之前。

首先ViewDidLoad执行处理,在执行过程中,他需要调用block,当block同步调用完成之后,这个ViewDidload才能继续向下走,ViewDidload方法处理结束,需要block调用完成以后。block任务要想执行,主队列是一个先进先出的性质,所以它要等到ViewDidLoad执行完成以后才能调用block的任务。这就造成了循环等待。

简单来讲就是,同步任务就是要等待队列中的上个任务完成以后才能调用这个同步任务。而这个同步任务,在上个任务之中。这就造成互相等待。

在主线程中开启一个GCD异步任务

在ViewDidLoad当中,在主线程中开启一个GCD异步任务。ViewDidLoad这个任务跟GCD异步任务并没有依赖关系,所以不会出现死锁

多线程的面试真题

-(void)GCDTest1{
    //全局并发队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //当前线程第一个打印日志1
    NSLog(@"1");
    //同步任务到全局队列中,所以dispatch_sync里面的代码会在主线程中执行。
    dispatch_sync(q, ^{
         //第二个打印日志2
        NSLog(@"2");
        //同步任务,会执行
        dispatch_sync(q, ^{
              //第三个打印日志3
            NSLog(@"3");
        });
        //第四个 打印日志4
        NSLog(@"4");
    });
    //第五个打印日志5
    NSLog(@"5");
}

异步串行

dispatch_async(dispatch_get_main_queue(), ^{
    
    NSLog(@"经常用到的主线程操作");
    
});

异步并发

-(void)GCDTest3{
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        //这个方法不会运行,因为runloop在子线程中默认是不开启的,所以用这句话不会执行
        [self performSelector:@selector(printlog) withObject:nil afterDelay:0];
        NSLog(@"3");
    });
    
}

打印结果是1、3。

如何运用dispatch_barrier_async(栅栏函数)

对一个磁盘DB进行读写,如何设计多读单写?

  • 读者、读者并发
  • 读者、写者互斥
  • 写者、写者互斥

用一个dispatch_barrier_async栅栏放入到并发队列来处理写操作。

- (id)GG_objectForkey:(NSString *)key{
    
    __block id obj;
    //并发任务就是可以多个执行
    dispatch_sync(concurrent_queue, ^{
        obj = [self->userCenterDic objectForKey:key];
    });
    //同步操作能立刻返回结果
    return obj;
}


-(void)GG_setObject:(id)obj forKey:(NSString *)key{
    //异步栅栏调用设置数据
    dispatch_barrier_sync(concurrent_queue, ^{
        [self->userCenterDic setObject:obj forKey:key];
    });
}

调度组dispatch_group_async

使用GCD实现这个需求:A、B、C是那个任务并发,完成后执行任务D?

- (void)handle{
    
    dispatch_group_t group = dispatch_group_create();
    for (NSURL *url in arrayURLs) {
        
        dispatch_group_async(group, concurrent_queue, ^{
            NSLog(@"url is %@",url);
        });
        

    }
    dispatch_group_notify(group, concurrent_queue, ^{
       //当调度组中的任务全部完成以后才会调用这个函数
    });
    
}

NSOperation

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

优势:

  • 添加移除任务依赖,这些特点NSthread不具备,GCD不好实现的
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
  • 任务执行状态控制
  • 最大并发量

任务执行状态控制

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

状态控制

  • 如果只重写了main方法,系统的底层实现会控制变更任务执行完成状态,以及任务退出
  • 如果重写了start方法,自行控制任务状态,在合适的时机更新状态
    • 重写了start方法就是相当于,将系统控制状态的代码覆盖了。

NSOperation的源码

gnustep-base

系统是如何移除一个isFinished=YES的NSOperation的?

NSOperationQueue通过KVO来移除NSOperation的。

NSthread

启动流程

start -》创建pthread -〉 main -》 (RunLoop事件的运行循环)[target performselect:selector]->exit

NSthread创建流程.png

如果需要写一个常驻的线程需要我们在Select方法里面实现常驻runloop。

经常使用的锁

  • @synchchronized

  • atomic

  • OSSpinLock

  • NSRecursiveLock

  • NSLock

  • dispatch_semaphore_t

  • @synchchronized

    • 在创建单例对象的时候使用,来保证在多线程环境下,创建的对象是唯一的。
  • atomic

    • 修饰属性的关键字
    • 对被修饰对象进行原子操作(不负责使用)
      • 只负责赋值,不负责对象使用
  • OSSpinLock

    • 自旋锁,循环等待询问,不是放当前资源
    • 现在已经不安全了,优先级反转
    • 现在推荐使用,pthread的mutex
    • 专门用于轻量级数据访问,简单的int值 +1/-1操作
    • 从系统的runtime去学习,工作中很少用
  • NSLock

    • 加锁
    • 逻辑
    • 解锁
  • NSRecursiveLock

    • 递归锁,可以重入
    • 加锁和解锁是成对出现的。
  • dispatch_semaphore_t

    • 信号量,记录型信号量
    • create操作
      • struct semaphore
        • int value
        • list <thread>
    • wait -1 2个参数 信号量是哪个 等待时长
      • 内部操作是, value - 1
      • 当value小于0的时候 阻塞是一个主动行为
    • signal +1
      • value +1
      • value 仍然《=0 说明在释放信号之前还有线程还在排队,然后就会去把相应的线程唤醒,这样就可以实现一个线程同步,唤醒是一个被动的行为。由释放信号的线程来唤醒被阻塞的线程

多线程面试问题总结

怎么用GCD实现多读单写?

dispatch_barrier_async的使用

iOS系统为我们提供了几种多线程技术各自的特点

NSthread

实现常驻线程。

GCD

实现一些简单的线程同步,子线程的分派,多读单写

NSOperation和NSOperationQueue

第三方框架 afn sdweb
任务状态控制
添加依赖

NSOperation 对象在Finished之后是怎么样从Queue中当移除掉的

  • 通过KVO实现的,去通知Queue去移除 NSOperation

你都用过哪些锁?结合实际谈谈你是怎样使用的?

  • NSLock
  • NSRecursiveLock
  • dispatch_smphore_t
  • synchchronized
  • atomic
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • CPU不同调度:一个进程可以开启多条线程,每条线程可以并行执行不同的任务。任务执行完毕依赖于线程池:不一定按照固定...
    StevenHu_Sir阅读 243评论 0 1
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 859评论 0 3
  • 我只是一个搬运工,仅仅为了加深记忆,感谢作者分享,文章大部分来源:尚大大o_O 线程和进程 几乎所有的操作系统都支...
    zhu哥哥阅读 112评论 0 1
  • 一、知识结构分析 多线程之间的关系 pthread是POSIX线程的API NSThread是Cocoa对pthr...
    huoshe2019阅读 504评论 0 4
  • 之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家。(题...
    Job_Yang阅读 12,111评论 12 143