iOS多线程
iOS中常见的多线程方案
pthread
是一套通用的多线程API,适用于Unix/Linux/Windows等系统,跨平台/可移植,使用难度比较大,生命周期由程序员管理NSThread
是一个面向对象的线程类,简单易用可以直接操作线程对象,生命周期由程序员管理GCD
是为了替代NSThread
等线程技术,充分利用设备的多核,生命周期由系统自动管理NSOperation
是对GCD
的封装,使用起来更加面向对象,生命周期由系统自动管理
本文主要以GCD
为例来阐述。
GCD
的常用函数
同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)
;
queue
:队列;block
:任务;
用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
;
GCD
的队列
GCD
的队列可以分为两大类型
- 并发队列 -
Concurrent Dispatch Queue
- 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步
dispatch_async
函数下才有效
- 串行队列 -
Serial Dispatch Queue
- 让任务一个接一个的执行(一个任务执行完毕后,再执行下一个任务)
同步、异步、并发、串行
- 同步和异步主要影响:能不能开启新的线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力
- 并发和串行主要影响:任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务完毕后,再执行下一个任务
各种队列的执行效果
- 使用
sync
函数往当前串行队列中添加任务,会卡主当前的串行队列(产生死锁)
1 | -(void)viewDidLoad{ |
上面这段代码会有产生上述问题,导致死锁。
多线程安全隐患
- 资源共享
- 1块资源可能被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件
- 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
那么该如何解决上述问题吗?
答案是:线程同步,使用线程同步技术,协同步调,按预定的先后次序进行;
常见的线程同步技术是:加锁
iOS中的线程同步方案
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondation
NSCondationLock
@synchronized
接下来我们一一介绍
OSSpinLock
叫做自旋锁,等待锁的线程会处于“忙等(busy-wait)”状态,一直占用着CPU资源
注意:这种锁目前已经不安全,可能会出现优先级反转问题
- 如果等待说的优先级比较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁
- 需要导入头问题件
#import<libkern/OSAtomic.h>
使用范例:
1 | // 初始化 |
os_unfair_lock
用于取代不安全的OSSpinLock
,从iOS10才支持- 从底层调用看,等待
os_unfair_lock
锁的线程会处于休眠状态,并非忙等 - 需要导入头文件
#import<os/lock.h>
- 从底层调用看,等待
使用范例:
1 | //初始化 |
pthread_mutex
mutex
叫做“互斥锁”,等待锁的线程会处于休眠状态- 需要导入头文件
#import"<pthread.h>"
1 | // 初始化锁的属性 |
pthread_mutex - 递归锁
1 | // 初始化所得属性 |
pthread_mutex - 条件
1 | // 初始化锁 |
系统封装起来的锁:NSLock、NSRecursiveLock、NSCondition、NSConditionLock
- NSLock是对
mutex
普通锁的封装 - NSRecursiveLock也是对
mutex
递归锁的封装,API跟NSLock
基本一致 - NSConditon是对
mutex
和cond
的封装 - NSConditionLock是对
NSCondition
的进一步封装,可以设置具体的条件值
- dispatch_semaphore
- semaphore叫做信号量,信号量的初始值可以用来控制线程并发访问的最大数量,信号量初始值为1,代表同时只允许1条线程访问资源,保证线程同步。
1 | // 信号量初始值 |
- dispatch_queue
直接用GCD的串行队列,也是可以实现线程同步的
1 | dispatch_queue_t queue = dispatch_queue_create("lcok_queue",DISPATCH_QUEUE_SERIAL); |
@synchronized 是对mutex递归锁的封装,@synchronized(obj)内部会生成对obj对应的递归锁,然后进行加锁、解锁操作
1
2
3@synchronized(obj){
//task
}
iOS线程同步方案性能比较
性能从高到低排序:1
2
3
4
5
6
7
8
9
10
11os_unfair_lock
OSSpinLock
dispatch_semaphore
pthread_mutex
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSCondition
pthred_mutex(recursive)
NSRecursiveLock
NSConditionLock
@synchronized
自旋锁、互斥锁比较
- 什么情况使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU资源不紧张
- 多核处理器
- 什么情况使用互斥锁比较划算?
- 预计线程等待锁的时间较长
- 单核处理器
- 临界区有IO操作
- 临界区代码复杂或者循环量大
- 临界区竞争很激烈
atomic
atomic用于保证属性setter、getter的原子操作,相当于在getter和setter内部家里线程同步的锁,它并不能保证属性的使用过程是线程安全的
iOS中的读写安全方案
同一时间,只允许一个线程进行写操作,允许有多个线程进行读操作,并且不允许既有写的操作又有读的操作。这种场景就是典型的”多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有:
pthread_rwlock:读写锁
dispatch_barrier_async:异步栅栏调用