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.pngpthread:
即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。
网友评论