iOS多线程实现

作者: 讷于文 | 来源:发表于2017-02-21 10:41 被阅读53次

    以下是对iOS实现多线程的介绍,阅读前需先对线程有一定的了解

    线程生命周期

    线程生命周期

    iOS实现多线程的方式

    NSThread

    GCD

    NSOperation

    NSThread实现多线程

    NSThread是线程类,创建一个NSThread就是创建一个线程

    NSThread创建线程的几种方式:

    + (void)detachNewThreadWithBlock:(void (^)(void))block;

    - (instancetype)initWithBlock:(void (^)(void))block

    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

    - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument

    detach开头的类方法,在线程创建 的同时,会运行线程。

    实例初始化的线程,则需要主动调用start方法,启动线程。调用start方法之后,线程立即进入就绪状态,等待系统的调度,然后线程就在运行和就绪状态之间来回切换,直到事务完成。

    这里的block和selector是线程需要执行的事务,当事务执行完成之后该线程就可以被释放了(死亡状态)。

    除了事务执行,在线程出错,或者调用thread的exit方法后,线程也会dead。

    线程状态

    isExecuting:是否正在执行

    isFinished:是否结束

    isCanceled:是否取消

    调用Thread的cancel方法,并不能真的取消线程事务,只能把isCanceled变成YES

    如果需要取消线程,可以向线程发送一个信号(比如调用cancel方法,把isCanceled变成YES),在线程事务中判断这个信号,当线程收到终止信号,程序终止事务或者调用exit(例如事务是一个循环,在循环体中必要的地方判断isCanceled是否为YES,当为YES的时候跳出循环体,终止事务)。

    尽量不要用exit结束线程(具体原因忘记了,等找到,再补回来)

    线程睡眠

    调用sleepXX 方法可以暂停线程一段时间,使线程进入阻塞状态。

    + (void)sleepUntilDate:(NSDate *)date;

    + (void)sleepForTimeInterval:(NSTimeInterval)ti;

    线程优先级

    threadPriority代表线程优先级,是一个double类型,区间为:0~1,1代表最高优先级,0代表最底优先级。

    NSThread默认优先级是0.5。

    优先级高的线程获取更多的执行机会。

    线程同步与线程通信

    使用@synchronized代码块实现同步

    被@synchronized修饰的代码块可简称为同步代码块。

    @synchronized(obj) {

    ...

    }

    同步代码块是为了防止多个线程同时访问同一个资源(obj),

    obj就是同步监视器,只有获得了对obj的锁定(或者资源访问机会,类似对obj加了同步锁),才能执行下面的代码块。

    要正确的选用监视器(obj)

    代码块里的代码要尽可能的短,最好只放入访问和修改obj 的代码

    同步锁(NSLock)

    NSLock也是控制多线程对临界资源访问的工具,每次只有一个线程可以NSLock进行锁定。

    [lock lock];

    ...

    [lock unlock];

    synchronized和NSLock锁的是什么?谁才是临界资源?

    lock是锁,临界资源放在锁中间,大家都遵守协议(只有获取到锁,才可以访问临界资源)才能做到对临界资源的同步访问。

    NSRecursiveLock(递归锁)

    NSCondition线程通信

    以上的同步方法都是被动同步,当线程访问的资源被锁定以后,之后被动排队,等待资源释放。NSCondition是线程的主动同步,例如:线程A的任务是基于线程B的任务的结果之上的,线程A利用NSCondition主动等待,当线程B的任务完成以后,利用NSCondition通知线程A,线程A收到通知后结束等待,完成自己的任务。

    NSCondition也实现了NSLocking协议,所以NSCondition也可以当做锁来使用,并在锁的基础上加了wait、signal,broadcast等功能。普通的lock实现的被动同步是无序的,谁先获得资源谁先执行,加入了wait、signal,broadcast功能的Condition可以实现有序的同步。

    例如,生产者和消费者同步。

    使用NSLock,如果消费者先获取到产品库的使用权,会先消费,但是这时候还没有生产,产品库是空的,消费不了,然后释放使用权,接着生产者获取产品库使用权,生产产品。这显然不是一个正确的顺序。

    使用NSCondition,如果消费者先获取到产品库的使用权,判断是否有产品,没有就wait等待,然后生产者获取使用权,开始生产,完成后signal通知消费者。消费者收到通知,开始消费。

    - (void)wait;

    让当前线程等待

    - (void)signal;

    通知某一个等待的线程,可以继续了

    - (void)broadcast;

    通知所有等待的线程,可以继续了

    GCD实现多线程

    GCD的优点

    GCD可以管理线程。使用GCD实现多线程只需两步,创建队列,将任务提交给队列。队列负责管理开发者提交的任务,每一个队列都已一个线程池,用来管理线程。 控制线程的同步、并发、生命周期管理是一个非常复杂的过程,而对于开发者来说,更关心的是待处理的任务。GCD队列对多线程进行了封装,使得开发者从管理多线程的复杂工作中脱离出来,把每一个任务封装成一个block工作单元。开发者把这些工作单元放入GCD队列中,由队列来进行创建、管理线程的工作(这个过程对开发者不可见),这样开发者就可以专心处理自己的任务了。

    队列的种类:

    串行队列:线程池只有一个线程,任务以串行执行。

    并发队列:线程池有多个线程,任务以FIFO的顺序并发启动执行。

    创建队列

    dispatch_queue_t dispatch_get_current_queue(void);

    获取当然任务所在的队列

    dispatch_queue_t dispatch_get_main_queue(void)

    获取主队列:(一个串行队列,只有一个UI线程)

    dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

    获取系统全局并发队列。可以指定优先级。

    dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

    创建一个串行或者并发队列

    向队列提交任务

    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

    异步向队列提交任务。

    void dispatch_apply(size_t iterations, dispatch_queue_t queue,

    DISPATCH_NOESCAPE void (^block)(size_t));

    异步向队列提交多个相同的任务。串行的时候,可以理解为一个任务执行了多次,但是并发的时候,这几个任务是并发执行的。

    void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

    某个时刻,异步向队列提交任务。

    NSOperation&NSOperationQueue实现多线程

    优势

    NSOperationQueue的实现原理和GCD类似,NSOperationQueue也有一个线程池,来管理多线程的操作,开发者只需要关注与任务的分发。与GCD不同的是,GCD的接口是C类型接口,NSOperationQueue的接口是面向对象类型的接口。GCD的每个任务是一段代码段,NSOperationQueue的每个任务是一个NSOperation对象。GCD的任务对比与NSOperation的优势是,简单方便,创建一个任务只需要一段断码段即可,而NSOperation需要创建一个对象,复杂的任务还需要实现一个NSOperation子类。GCD任务的劣势是,无法对任务进行控制,当一个任务提交到队列上以后,很难取消该任务,或者获取该任务的进度。而NSOperation可以有效控制任务的进度,甚至可以灵活地取消任务,甚至整个队列的任务都能取消。而且NSOperationQueue很容易控制最大并发数,GCD队列控制起来就比较复杂。NSOperationQueue还可以灵活地暂停任务的分发。

    NSOperationQueue可以设置最大并发数,当最大并发数为1的时候,就是串行队列,>1的时候,就是并发队列。

    队列

    - (void)addOperation:(NSOperation *)op;

    - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);

    - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

    提交任务,任务也可以是一个简单的代码块。

    @property NSInteger maxConcurrentOperationCount;

    最大并发数

    @property (getter=isSuspended) BOOL suspended;

    暂停

    @property (nullable, assign ) dispatch_queue_t underlyingQueue;

    底层对应的GCD队列

    - (void)cancelAllOperations;

    取消队列中所有任务

    - (void)waitUntilAllOperationsAreFinished;

    阻塞当前线程,知道队列中的所有任务完成。

    任务(操作)

    NSOperation一般不会直接拿来用,而是实现它的子类,或者是用现有的几个简单的子类NSBlockOperation、NSInvocationOperation(以代码块、函数作为任务)。

    - (void)start;

    开始任务

    - (void)main;

    任务主体,子类要重写这个方法来实现自己的任务

    @property (readonly, getter=isCancelled) BOOL cancelled;

    是否被取消,由cancel方法来改变这个值

    - (void)cancel;

    取消任务,这个和NSThread里的cancel一样,只能改变cancelled的值,具体取消需要子类来完成。

    @property (readonly, getter=isExecuting) BOOL executing;

    是否正在执行

    @property (readonly, getter=isFinished) BOOL finished;

    是否执行完成

    @property (readonly, getter=isConcurrent) BOOL concurrent;

    是否并发

    @property (readonly, getter=isAsynchronous) BOOL asynchronous

    是否异步

    @property (readonly, getter=isReady) BOOL ready;

    是否就绪

    - (void)addDependency:(NSOperation *)op;

    - (void)removeDependency:(NSOperation *)op;

    添加、移除,“基于”任务。(该任务是基于这些任务的,只有这些基础任务执行完,该任务才可以执行)

    @property (nullable, copy) void (^completionBlock)(void)

    任务结束回调,当任务结束时调用。

    相关文章

      网友评论

        本文标题:iOS多线程实现

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