iOS线程锁及其性能

分享是每个优秀的程序员所必备的品质


注意:性能是在多线程环境中测试的结果!!!

内容提要:

  • 基本概念
  • 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 copy];
        _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

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_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_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);
        });
}



RCLockDemo

你可能感兴趣的:(iOS线程锁及其性能)