美文网首页底层
iOS-OC底层20:iOS多线程

iOS-OC底层20:iOS多线程

作者: MonKey_Money | 来源:发表于2020-11-02 17:53 被阅读0次

    1.多线程概念

    线程

    线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
    进程要想执行任务,必须得有线程,进程至少要有一条线程
    程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程

    进程

    进程是指在系统中正在运行的一个应用程序
    每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
    通过“活动监视器”可以查看 Mac 系统中所开启的进程

    进程与线程的关系

    地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
    资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。
    1: 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进 程都死掉。所以多进程要比多线程健壮。
    2: 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
    3: 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    4: 线程是处理器调度的基本单位,但是进程不是。 5: 线程没有地址空间,线程包含在进程地址空间中

    多线程的意义

    多线程是一个进程中并发多个线程同时执行各自的任务。就是由单核CPU通过时间片不断的切换执行程序。
    分时操作系统会把CPU的时间划分为长短基本相同的时间区间(时间片),在一个时间片内,CPU只能处理一个线程中的一个任务,对于一个单核CPU来说,在不同的时间片来执行不同线程中的任务,就形成了多个任务在同时执行的“假象”。

    时间片

    时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片

    • (单核CPU)同一时间,CPU 只能处理 1 个线程
    • 换言之,同一时间只有 1 个线程在执行
    • 多线程同时执行:
    • 是 CPU 快速的在多个线程之间的切换
    • CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
    • 如果线程数非常多
    • CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源
    • 每个线程被调度的次数会降低,线程的执行效率降低

    优点

    • 能适当提高程序的执行效率
    • 能适当提高资源的利用率(CPU,内存)
    • 线程上的任务执行完成后,线程会自动销毁

    缺点

    • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
    • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
    • 线程越多,CPU 在调用线程上的开销就越大
    • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

    iOS线程与Runloop的关系

    1.线程与Runloop是一一对应的,一个Runloop对应一个核心线程,为什么说是核心线程,因为Runloop是可以嵌套的,但是核心只有一个,他们的对应关系保存在一个全局字典里
    2.Runloop是用来管理线程的,线程执行完任务时会进入休眠状态,有任务进来时会被唤醒,开始执行任务。所以说Runloop是事件驱动的。
    3.Runloop在第一次获取时被创建,线程结束时被销毁。主线程的Runloop在程序启动的时候就会被创建。
    4.对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线 程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

    2.多线程技术方案

    image.png

    pthread:

    即POSIX Thread,是线程的POSIX标准,是一套通用的多线程API,可以在Unix/Linux/Windows等平台跨平台使用。iOS中基本不使用。

    NSThread:

    苹果封装的面向对象的线程类,可以直接操作线程,比起GCD,NSThread效率更高,由程序员自行创建,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。

    GCD:

    全称Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,GCD会自动利用更多的CPU内核,自动管理线程的声明周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。

    NSOperation:

    基于GCD封装的面向对象的多线程类,相较于GCD提供了很多方便的API,使用频率较高。

    多线程的生命周期

    image.png

    新建:实例化线程对象
    就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
    运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
    阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
    死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象

    3.可调度线程池

    什么是线程池:
    提供一组线程资源用来复用线程资源的一个池子


    image.png

    线程池中常用参数释义:
    corePoolSize 线程池的基本大小(核心线程池大小)
    maximumPool 线程池最大大小
    keepAliveTime 线程池中超过corePoolSize数目的空闲线程的最大存活时间
    unit keepAliveTime参数的时间单位
    workQueue 任务阻塞队列
    threadFactory 新建线程的工厂
    handler 当提交任务数超过maximumPoolSize与workQueue之和时,任务会交给RejectedExecutionHandler来处理
    饱和策略:
    AboutPolicy 直接抛出RejectedExecutionExeception异常,阻止系统的正常运行
    CallerRunsPolicy 将任务回退到调用者
    DisOldestPolicy 丢掉等待最久的任务
    DisCardPolicy 直接丢弃任务
    注: 以上四种拒绝策略均实现的RejectedExencutionHandler

    线程间通讯

    image.png

    直接消息传递:

    通过performSelector的一系列方法,可以实现由某一线程指定在另外的线程上执行任务。因为任务的执行上下文是目标线程,这种方式发送的消息将会自动的被序列化

    全局变量、共享内存块和对象:

    在两个线程之间传递信息的另一种简单方法是使用全局变量,共享对象或共享内存块。尽管共享变量既快速又简单,但是它们比直接消息传递更脆弱。必须使用锁或其他同步机制仔细保护共享变量,以确保代码的正确性。 否则可能会导致竞争状况,数据损坏或崩溃。

    条件执行:

    条件是一种同步工具,可用于控制线程何时执行代码的特定部分。您可以将条件视为关守,让线程仅在满足指定条件时运行。

    Runloop sources:

    一个自定义的 Runloop source 配置可以让一个线程上收到特定的应用程序消息。由于 Runloop source 是事件驱动的,因此在无事可做时,线程会自动进入睡眠状态,从而提高了线程的效率

    Ports and sockets:

    基于端口的通信是在两个线程之间进行通信的一种更为复杂的方法,但它也是一种非常可靠的技术。更重要的是,端口和套接字可用于与外部实体(例如其他进程和服务)进行通信。为了提高效率,使用 Runloop source 来实现端口,因此当端口上没有数据等待时,线程将进入睡眠状态

    消息队列:

    传统的多处理服务定义了先进先出(FIFO)队列抽象,用于管理传入和传出数据。尽管消息队列既简单又方便,但是它们不如其他一些通信技术高效

    Cocoa 分布式对象:

    分布式对象是一种 Cocoa 技术,可提供基于端口的通信的高级实现。尽管可以将这种技术用于线程间通信,但是强烈建议不要这样做,因为它会产生大量开销。分布式对象更适合与其他进程进行通信,尽管在这些进程之间进行事务的开销也很高

    atomic与nonatomic 的区别

    nonatomic 非原子属性
    atomic 原子属性(线程安全),针对多线程设计的,默认值
    保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值) atomic 本身就有一把锁(自旋锁) 单写多读:单个线程写入,多个线程可以读取
    atomic:线程安全,需要消耗大量的资源 nonatomic:非线程安全,适合内存小的移动设备
    iOS 开发的建议
    所有属性都声明为 nonatomic 尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

    互斥锁

    当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。

    • 互斥锁小结
    • 保证锁内的代码,同一时间,只有一条线程能够执行!
    • 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
    • 互斥锁参数
    • 能够加锁的任意 NSObject 对象
    • 注意:锁对象一定要保证所有的线程都能够访问
    • 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个锁对象
      一般互斥锁有@ synchronized,NSLock, pthread_mutex, NSConditionLock, NSCondition, NSRecursiveLock.

    自旋锁

    是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。
    在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。自旋锁有atomic, OSSpinLock, dispatch_semaphore_t.

    4.补充C与OC的桥接

    *__bridge只做类型转换,但是不修改对象(内存)管理权;

    • __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为 Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用 CFRelease或者相关方法来释放对象;
    • __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象 转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

    相关文章

      网友评论

        本文标题:iOS-OC底层20:iOS多线程

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