分享是每个优秀的程序员所必备的品质
注意:性能是在多线程环境中测试的结果!!!
内容提要:
- 基本概念
- OSSpinLock (自旋锁)
- os_unfair_lock (自旋锁)
- dispatch_semaphore (信号量)
- pthread_mutex (互斥锁)
- NSLock (互斥锁、对象锁)
- NSCondition (条件锁、对象锁)
- NSConditionLock (条件锁、对象锁)
- NSRecursiveLock (递归锁、对象锁)
- pthread_mutex(recursive) (递归锁)
- @synchronized (条件锁)
基本概念
锁:为了保证共享数据操作的完整性和安全性而提出一种机制。
为什么要使用:
以在一个并发队列中,添加异步任务,任务中对用nonatomic
的字符串属性赋值为例:
代码:
// @property (nonatomic,copy)NSString *target;
- (void)lockTest{
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
for(NSInteger i=0;i<1000;i++){
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"target--%ld",I];
});
}
}
运行结果:
坏内存访问.png
运行结果崩溃:向一个已经释放的对象发送消息。
为什么会这样呢?很简单,这是一个赋值过程调用了target的set方法,内部实现:
- (void)setTarget:(NSString *)target{
if(_target != target){
[_target release];
[target retain];
_target = target;
}
}
因为是异步执行的,有些线程执行到[_target release]
的时候,对象已经释放了;此时另外某些线程也执行[_target release]
的时候,就会向一个已经释放的对象发送消息造成崩溃。
针对以上问题,提出了锁的机制。在某一线程准备执行赋值操作时,加锁,赋值结束再解锁。其他线程无法访问已经加锁的资源。
iOS中提供了以下10种锁:
一、OSSpinLock
自旋锁:和互斥锁类似为了保证线程安全,两者在调度机制上略有不同,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。 OSSpinLock原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务所以,此锁比较适用于锁的持有者保存时间较短的情况下。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
之前的YY大神ibireme也提到了关于OSSpinLock会发生优先级反转而不安全:不再安全的 OSSpinLock
优先级反转(Priority Inversion): 简单来说由于多进程共享资源,具有最高优先权的进程被低优先级进程阻塞,反而使具有中优先级的进程先于高优先级的进程执行,导致系统的崩溃。
例如有任务A、B、C对应高、中、低三个优先级。C占有一个资源X,A也想要访问X,此时又有B要执行,B优先级高于C,于是占有资源的任务会被挂起,但是资源仍被占有中,资源得不到释放,导致A一直无法执行,但是优先级比A低的B却可以执行,这就是优先级反转。
// 初始化 OS_SPINLOCK_INIT 默认不加锁为0,加锁不为0
OSSpinLock spinLock = OS_SPINLOCK_INIT
// 加锁
OSSpinLockLock(&spinLock)
// 解锁
OSSpinLockUnlock(&spinLock)
// 尝试加锁
OSSpinLockTry(&spinLock)
代码:需要导入头文件#import <libkern/OSAtomic.h>
double begin;
__block double end;
NSInteger count = 1000;
dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
{
begin = CACurrentMediaTime();
__block OSSpinLock lock = OS_SPINLOCK_INIT;
for(NSInteger i = 0;i < count; i++){
dispatch_group_async(group, queue, ^{
OSSpinLockLock(&lock);
self.target = [NSString stringWithFormat:@"target--%ld",i];
OSSpinLockUnlock(&lock);
});
}
dispatch_group_notify(group, queue, ^{
end = CACurrentMediaTime();
NSLog(@"OSSpinLock: %8.2f ms",(end - begin) * 1000);
});
}
还要一种使用场景:OSSpinLockTry(&lock)
:即尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO,trylock
即使加锁失败,也可以继续执行其他任务。正常情况下我们追求的就是必须要加锁成功才会考虑之后的任务,就没必要轮询 使用 trylock
。
注:苹果已经在iOS10.0以后废弃了这种锁机制,使用os_unfair_lock
替换
二、os_unfair_lock
自旋锁,苹果在iOS10.0以后用来替代OSSpinLock
,需要导入头文件#import <os/lock.h>
具体使用:
// 初始化
os_unfair_lock unfair_lock = OS_UNFAIR_LOCK_INIT
// 加锁
os_unfair_lock_lock(&unfair_lock)
// 解锁
os_unfair_lock_unlock(&unfair_lock)
// 尝试加锁
os_unfair_lock_trylock(&unfair_lock)
性能测试代码同OSSpinLock,替换对应锁代码即可。
三、dispatch_semaphore
GCD里面的信号量,使用的还是比较多的,实际上是通过限制线程并发的条数来控制线程安全的。
// 初始化 long value :线程并发执行的数量
dispatch_semaphore_t semaphore_t = dispatch_semaphore_create(1);
// 加锁
dispatch_semaphore_wait(semaphore_t,DISPATCH_TIME_FOREVER);
// 解锁
dispatch_semaphore_signal(semaphore_t);
性能测试代码同上,替换对应锁代码即可。
四、pthread_mutex(互斥锁)
互斥锁:当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。
使用需要导入头文件 #import <pthread/pthread.h>
// 初始化 ,提供两种方式
// 第一种
pthread_mutex_t mutex_t;
pthread_mutex_init(&mutex_t, NULL);
// 第二种,宏初始化
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
// 加锁
pthread_mutex_lock(&mutex_t);
// 解锁
pthread_mutex_unlock(&mutex_t);
// 尝试加锁,这里和上面不同的是:当可以加锁返回的是 0,否则返回一个错误
pthread_mutex_trylock(& mutex_t)
性能测试代码同上,替换对应锁代码即可。
五、NSLock
互斥锁,使用也比较多
// 初始化
NSLock *lock = [[NSLock alloc]init];
// 加锁
[lock lock];
// 解锁
[lock unlock];
// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
[lock tryLock];
性能测试代码同上。
六、NSCondition
是互斥锁和条件锁的结合体
NSCondition 的对象实际上充当线程中的锁和检查器,锁主要为了当检测条件时保护数据源;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。执行条件引发的任务当条件不成立时,线程会阻塞。在另一个线程向条件对象发出信号之前,它一直保持阻塞状态。
// // 初始化
NSCondition *condition= [[NSCondition alloc]init];
// 加锁
[condition lock];
// 解锁
[condition unlock];
/*
其他功能
wait 进入等待状态
waitUntilDate:让一个线程等待一定的时间
signal 唤醒一个等待的线程
broadcast 唤醒所有等待的线程
性能测试代码同上。
七、NSConditionLock
// 初始化
NSConditionLock *_conditionLock = [[NSConditionLock alloc]init];
// 加锁
[_conditionLock lock];
// 解锁
[_conditionLock unlock];
// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
[_conditionLock tryLock];
/*
其他功能接口
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; //初始化传入条件
- (void)lockWhenCondition:(NSInteger)condition;//条件成立触发锁
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//尝试条件成立触发锁
- (void)unlockWithCondition:(NSInteger)condition;//条件成立解锁
- (BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//触发锁 条件成立 并且在等待时间之内
*/
性能测试代码同上。
八、NSRecursiveLock
递归锁
可以被同一线程多次请求,而不会引起死锁。严格上来说只是互斥锁的一个特例,同样只能有一个线程访问该对象,但允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。
// 初始化
NSRecursiveLock *_recursiveLock = [[NSRecursiveLock alloc]init];
// 加锁
[_recursiveLock lock];
// 解锁
[_recursiveLock unlock];
// 尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO
[_recursiveLock tryLock];
/*
注: 递归锁可以被同一线程多次请求,而不会引起死锁。
即在同一线程中在未解锁之前还可以上锁, 执行锁中的代码。
这主要是用在循环或递归操作中。
- (BOOL)lockBeforeDate:(NSDate *)limit;//触发锁 在等待时间之内
*/
性能测试代码同上。
九、pthread_mutex(recursive)
// 初始化
pthread_mutex_t mutex_t;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁
pthread_mutex_init(&mutex_t, &attr);
pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在重新进行初始化之前该结构不能重新使用
// 加锁
pthread_mutex_lock(&mutex_t);
// 解锁
pthread_mutex_unlock(&mutex_t);
/*
注: 递归锁可以被同一线程多次请求,而不会引起死锁。
即在同一线程中在未解锁之前还可以上锁, 执行锁中的代码。
这主要是用在循环或递归操作中。
*/
性能测试代码同上。
十、@synchronized
相比于使用 其他方式例如NSLock 创建锁对象、加锁和解锁来说,@synchronized 用着更方便,可读性更高。
{
begin = CACurrentMediaTime();
NSObject *lock = [NSObject new];
for(NSInteger i = 0;i < count; i++){
dispatch_group_async(group, queue, ^{
@synchronized (lock) {
self.target = [NSString stringWithFormat:@"target--%ld",i];
}
});
}
dispatch_group_notify(group, queue, ^{
end = CACurrentMediaTime();
NSLog(@"@synchronized: %8.2f ms",(end - begin) * 1000);
});
}
网友评论