美文网首页
6.iOS开发之多线程

6.iOS开发之多线程

作者: 苹果我咬了一口 | 来源:发表于2020-03-23 16:05 被阅读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 和内存减负

    相关文章

      网友评论

          本文标题:6.iOS开发之多线程

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