6.iOS开发之多线程

2020-03-23  本文已影响0人  苹果我咬了一口

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)  主线程空闲时执行任务


文章参考或引用:

iOS多线程编程

『GCD』详尽总结

iOS 如何高效的使用多线程

iOS 任务调度器:为 CPU 和内存减负

上一篇下一篇

猜你喜欢

热点阅读