以下是对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)
任务结束回调,当任务结束时调用。