6.iOS开发之多线程
iOS日常开发几乎都离不开多线程,基本上常用的几个NSThread、GCD和NSOperation了,另外其实还有基于C语言的pthread,POSIX接口。日常整理一下下。
线程的概念
每个应用程序都是一个独立的进程,一个进程的所有任务都在线程中进行,因此一个进程中至少有一个线程。线程是程序执行流的最小单元,是进程中的一个实体,是执行程序最基本的单元,有自己栈和寄存器。
什么是多线程
多线程就是一个进程开启多条线程,让所有任务并发执行。一定意义上实现了进程内的资源共享,以及效率的提升。
Pthreads && NSThread
POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。
简单来说就是基于C的跨平台的操纵线程API。需要自己管理线程的生命周期。
NSThread
苹果提供的基于OC的API,封装了Pthreads,需要自己管理线程的生命周期,通常用的最多的是[NSThread currentThread]获取当前线程。
GCD(大中央调度)
封装了线程操作,提供简洁的C语言接口,使用简单高效,苹果官方推荐使用。自动管理了线程的生命周期以及任务的执行规则。
任务与队列
任务:就是执行操作的意思,在线程中执行的那段代码,GCD 是放在 block 中的。
常用的任务执行方法:
dispatch_sync同步与dispatch_async异步任务执行
两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
队列:统一管理任务的方式,队列都是按照FIFO的原则依次触发任务。分为串行队列与并行队列。队列只负责任务的调度,而不负责任务的执行,任务是在线程中执行的。(可以理解成任务是放在队列里面的,要被调度到线程中去执行)
串行队列(DISPATCH_QUEUE_SERIAL):所有任务会在一条线程中执行(同步任务则是当前线程,异步任务则新开辟线程),并且一个任务执行完毕后,才开始执行下一个任务。(等待完成)
主队列是系统创建的串行队列,任务都在主线程上执行。
并行队列(DISPATCH_QUEUE_CONCURRENT):可以开启多条线程并行执行任务(异步任务新开辟线程,同步任务不开启新线程),并且当一个任务放到指定线程开始执行时,下一个任务就可以开始执行了。(等待发生)
全局队列是系统创建好的一个并行队列,使用起来与自己创建的并行队列无本质差别。
串行队列里面有一个任务中调用同步方法就会造成死锁
经典例子:主队列中viewDidLoad任务调用sync就会死锁crash
关于多线程的同步异步任务与串行并行队列组合:同步在任何队列都不会开启新线程,在主线程会造成死锁,异步在并行可开启多条线程,在串行只开启一条线程,在主队列不开启线程。
GCD其他函数
1. dispatch_after 用于任务延时执行
2. dispatch_once 保证函数在整个生命周期内只会执行一次(多用于单例)
3. dispatch_group 队列组(调度组) dispatch_group_async添加任务 全部执行完调用dispatch_group_notify通知
dispatch_group_wait会阻塞当前线程
dispatch_group_enter标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
dispatch_group_leave标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
使用dispatch_group_enter与dispatch_group_leave的方式为多,例如dispatch_group_async里执行的是异步代码,这种方式会等待异步任务完成而用dispatch_group_async则不会。
4.dispatch_barrier_sync(堵塞当前队列与主队列中的所有任务) dispatch_barrier_async(只堵塞当前队列的任务)栅栏函数
必须是自定义的并发队列 必须是同一个队列 【保证顺序执行 保证线程安全】
5. dispatch_apply 重复执行某个任务,任务队列是并行队列则重复执行的任务会并发执行;为串行队列则任务会顺序执行;该函数为同步函数,要防止线程阻塞和死锁
6.dispatch_semaphore信号量
dispatch_semaphore_create创建信号量,初始值不能小于0;
dispatch_semaphore_wait等待降低信号量,也就是信号量-1;
dispatch_semaphore_signal提高信号量,也就是信号量+1;
dispatch_semaphore_wait和dispatch_semaphore_signal通常配对使用。
NSOperation && NSOperationQueue
苹果对于GCD的封装,NSOperation其实就是上面所说的任务,NSOperationQueue就是类似于GCD中的队列,用于管理任务。
NSOperation
抽象类,不能直接使用,我们要用他的两个子类NSBlockOperation与NSInvocationOperation。或者自定义子类重写main方法来实现相关功能。
NSOperationQueue
Operation加入队列之后不用调用任务的start方法,队列会管理任务的执行情况。
支持优先级(queuePriority),支持线程之间的依赖关系(addDependency)。
支持 KVO,可以监测 operation 是否正在执行(isExecuted)、 是否结束(isFinished)、是否取消(isCanceld)。
maxConcurrentOperationCount 控制并发数【队列中同时可调度任务】为1时并非为串行队列,队列任务的执行顺序依然取决于很多因素。
suspended队列可以挂起
cancelAllOperations取消队列中所有任务,取消后得重新添加任务
线程锁
多线程操作是多个线程并行的,所以同一块资源可能在同一时间被多个线程访问,所以为了线程安全,需要线程锁。自旋锁(会忙等)与互斥锁(会休眠),
OSSpinLock (自旋锁)会忙等,效率高,cpu消耗大
效率高 目前已经不再线程安全 不推荐使用
pthread_mutex(互斥锁) 会休眠
基于Linux的一套跨平台线程锁API,非常底层,可以创建条件锁,递归锁等。 使用递归锁能防止死锁,因为递归锁能够对同一把锁重复加锁。
dispatch_semaphore (信号量)
性能好,推荐使用
NSLock (pthread_mutex的一层封装) 对象锁
lock与unlock
@synchronized 递归锁
底层封装的pthread_mutex的PTHREAD_MUTEX_RECURSIVE 模式,锁对象来表示是否为同一把锁。
要传入一个同步对象(一般就是self),然后将你需要加锁的资源放入代码块中,如果该资源有线程正在访问时,会让其他线程等待
推荐使用pthread_mutex 与 dispatch_semaphore 性能高
多线程的优化思路
1、减少队列切换:过多的上下文切换会带来资源开销。
2、控制线程数量:GCD 中并行队列并不能限制线程数量,可以创建多个串行队列来模拟并行的效果。
3、线程优先级权衡:qualityOfService
iOS 8 过后设置队列优先级的方法如下:
dispatch_queue_attr_t attr=dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT,QOS_CLASS_BACKGROUND,0);
dispatch_queue_t queue=dispatch_queue_create("x.x.x",attr);
这里就设置了一个QOS_CLASS_BACKGROUND优先级,比较适合后台异步下载大文件之类的业务。
4、主线程任务的优化
(1)内存复用 比如UITableViewCell 的复用
(2)懒加载任务: UI 组件必须在主线程初始化,那么就需要用时再初始化。
(3)任务拆分排队执行
通过监听 Runloop 即将结束等通知,将大量的任务拆分开来,在每次 Runloop 循环周期执行少量任务。其实在实践这种优化思路之前,应该想想能不能将任务放到异步线程,而不是用这种比较极端的优化手段。
(4) 主线程空闲时执行任务
文章参考或引用: