多线程方案
pthread:一套C语言通用多线程API,跨平台,使用难度较大,需要开发者管理生命周期,iOS中几乎用不到
NSThread:基于pthread的封装,面向对象,同样需要开发者管理生命周期,iOS中偶尔使用
GCD:能充分利用设备多核,提高效率,C语言API,自动管理生命周期,iOS经常使用
NSOperation:基于GCD的封装,使用更加面向对象,OC语言,自动管理生命周期,iOS经常使用
以上方案的使用方法,这里不展开做介绍,本篇文章主要回顾iOS中锁相关知识。
多线程安全隐患
当多个线程访问同一块资源时,容易引发数据安全问题。常见的场景:银行存取钱的计算,多窗口售票等等,都映射到多线程的安全隐患。
举个例子:
当整型17被线程A和线程B先后读取并进行了+1操作,理论上最后得到的结果应该是19,因为进行了两次+1,但是结果就出现了异常,这就是数据错乱。解决方案就是使用线程同步技术,给定先后次序进行操作,常见的线程同步技术就是加锁。
加锁后保证仅有一条线程对17进行读和写,待线程操作完成解锁后后面线程才可进行读写操作,这样一来得到的数据就正确了。
线程同步方案
OSSpinLock:自旋锁,等待锁的线程会处于忙等状态,一直占用CPU资源,目前已经不再安全,可能会出现优先级反转问题。
os_unfair_lock:用于取代不安全的OSSpinLock,iOS10开始支持,并且等待os_unfair_lock锁的线程会处于休眠状态,而不是忙等状态,一定程度上节省CPU资源,使用需要导入<os/lock.h>
pthread_mutex:互斥锁,等待锁的线程会处于休眠状态,需导入<pthread.h>
普通锁:
递归锁:
条件锁:
NSLock:是对mutex普通锁的封装,使用更加面向对象
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock:NSObject< NSLocking>
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
// 初始化化锁
NSLock *lock = [NSLock new];
NSRecursiveLock:是对mutex递归锁的封装,API与NSLock基本一致
NSCondition:是对mutex和cond的封装
@interface NSCondition:NSObject<NSLocking>
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@end
NSConditionLock:是对NSCondition的进一步封装
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name;
@end
dispatch_semaphore:信号量,可以控制并发访问最大数量,信号量初始化值为1代表同时只能有1条线程访问资源用于确保线程同步。
//信号量初始值
int value = 1
//初始化信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
//如果信号量值<=0,当前线程就会进入休眠等待,直到信号量>0
//如果当前信号量>0 就减1,然后执行后面代码
dispatch_semphore_wait(semaphore,DISPATCH_TIME_FOREVER)
//让信号量值+1
dispatch_semphore_signal(semaphore)
@synchronized:是对mutex封装,@synchronized(obj)内部生成obj对应的递归锁,然后进行加锁解锁操作。
自旋锁和互斥锁的比较
什么情况下使用自旋锁:
1、预计线程等待锁的时间很短
2、多核处理器
3、CPU资源不紧张
4、加锁的代码(临界区)经常被调用,但是很少出现竞争资源情况
什么情况下使用互斥锁:
1、预计线程等待锁的时间比较长
2、单核处理器
3、临界区有IO操作
4、临界区竞争非常激烈
5、临界区代码复杂或者循环量大