iOS开发多线程中的锁

前言

  在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题。解决资源争用,最直接的想法是引入,对并发读写的数据进行保护,保证每次只有一个线程访问这一块资源。
  是最常用的同步工具:一块公共资源在同一个时间只能允许被一个线程访问,比如一个线程A进入加锁资源之后,由于已经加锁,另一个线程B就无法访问,只有等待前一个线程A执行完后解锁,B线程才能访问加锁资源。

为什么需要锁

 以常见的火车站卖票为例,假设有20张票,有两个窗口同时售票:

- (void)ticketTest{
    self.ticketsCount = 20;
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 线程1
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
                [self sellingTickets];//多线程售票
            }
    });
    // 线程2
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
                [self sellingTickets];//多线程售票
            }
    });
}

- (void)sellingTickets{
    NSInteger oldMoney = self.ticketsCount;
    sleep(0.2);
    oldMoney -= 1;
    self.ticketsCount = oldMoney;
    NSLog(@"当前剩余票数-> %ld", oldMoney);
}

测试得到的结果为

2019-02-21 17:10:40.861358+0800 多线程测试[8167:20874670] 当前剩余票数-> 19
2019-02-21 17:10:40.861358+0800 多线程测试[8167:20874671] 当前剩余票数-> 19
2019-02-21 17:10:40.861723+0800 多线程测试[8167:20874671] 当前剩余票数-> 18
2019-02-21 17:10:40.861723+0800 多线程测试[8167:20874670] 当前剩余票数-> 18
2019-02-21 17:10:40.861851+0800 多线程测试[8167:20874670] 当前剩余票数-> 17
2019-02-21 17:10:40.861961+0800 多线程测试[8167:20874670] 当前剩余票数-> 16
2019-02-21 17:10:40.861989+0800 多线程测试[8167:20874671] 当前剩余票数-> 16
2019-02-21 17:10:40.862066+0800 多线程测试[8167:20874670] 当前剩余票数-> 15
2019-02-21 17:10:40.862222+0800 多线程测试[8167:20874670] 当前剩余票数-> 14
2019-02-21 17:10:40.863234+0800 多线程测试[8167:20874671] 当前剩余票数-> 13
2019-02-21 17:10:40.863958+0800 多线程测试[8167:20874670] 当前剩余票数-> 12
2019-02-21 17:10:40.864225+0800 多线程测试[8167:20874671] 当前剩余票数-> 11
2019-02-21 17:10:40.864529+0800 多线程测试[8167:20874670] 当前剩余票数-> 10
2019-02-21 17:10:40.865159+0800 多线程测试[8167:20874671] 当前剩余票数-> 9
2019-02-21 17:10:40.865498+0800 多线程测试[8167:20874670] 当前剩余票数-> 8
2019-02-21 17:10:40.865777+0800 多线程测试[8167:20874671] 当前剩余票数-> 7
2019-02-21 17:10:40.866747+0800 多线程测试[8167:20874670] 当前剩余票数-> 6
2019-02-21 17:10:40.866970+0800 多线程测试[8167:20874671] 当前剩余票数-> 5
2019-02-21 17:10:40.867402+0800 多线程测试[8167:20874671] 当前剩余票数-> 4
2019-02-21 17:10:40.867879+0800 多线程测试[8167:20874671] 当前剩余票数-> 3

不加锁时很明显数据发生了混乱。

iOS中都有哪些锁?

从大的方向讲有两种锁:

  • 互斥锁
  • 自旋锁。

这两种类型下分别有自己对应的锁:


锁的分类

互斥锁和自旋锁的对比:

这两种锁的相同点不必多说,都可以避免多线程访问同一个值发生混乱,重点说一下两种的不同点:

  • 互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁, 则等待资源的线程会被唤醒

  • 自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁, 则等待资源的线程会立即执行

自旋锁的特点:

  1. 自旋锁的性能高于互斥锁,因为响应速度快
  2. 自旋锁虽然会一直自旋等待获取锁,但不会一直占用CPU,超过了操作系统分配的时间片会被强制挂起
  3. 自旋锁如果不能保证所有线程都是同一优先级,则可能造成死锁。

因为以上的特点,自旋锁和互斥锁也有不同的使用场景:

多核处理器情况下: 如果预计线程等待锁的时间比较短,短到比线程两次切换上下文的时间还要少的情况下,自旋锁是更好的选择。
如果时间比较长,则互斥锁是比较好的选择。 单核处理器情况下: 不建议使用自旋锁。

从详细来分锁:

  • @synchronized
  • NSLock 对象锁
  • NSRecursiveLock递归锁
  • NSConditionLock 条件锁
  • pthread_mutex 互斥锁(C语言)
  • dispatch_semaphore 信号量实现加锁(GCD
  • OSSpinLock 自旋锁

锁的使用

1. OSSpinLock 自旋锁

实现机制:忙等
使用方式:

#import <libkern/OSAtomic.h>
// 初始化 OS_SPINLOCK_INIT默认值为 0,在 locked 状态时就会大于 0,unlocked状态下为 0
OSSpinLock spinLock = OS_SPINLOCK_INIT;
// 加锁
OSSpinLockLock(&spinLock);
//你需要保护的操作
    {}
// 解锁
OSSpinLockUnlock(&spinLock);

但不幸的是OSSpinLock已经在iOS10被苹果弃用,因为它存在优先级反转的问题,故不再细讲,只需要知道其大概的原理就OK。

优先级反转:
发生在低优先级线程拿到锁时,高优先级线程进入忙等(busy-wait)状态,消耗大量CPU 时间,
从而导致低优先级线程拿不到 CPU 时间,也就无法完成任务并释放锁。

那为什么忙等会导致低优先级线程拿不到时间片?

现代操作系统在管理普通线程时,通常采用时间片轮转算法(Round Robin,简称 RR)。每个线程会

被分配一段时间片(quantum),通常在 10-100 毫秒左右。当线程用完属于自己的时间片以后,就会
被操作系统挂起,放入等待队列中,直到下一次被分配时间片。

具体的解释不再安全的 OSSpinLock

2. os_unfair_lock

这是苹果iOS10之后推出的新的取代OSSpinLock的锁。虽然是替代OSSpinLock的,但os_unfair_lock并不是自旋锁,根据苹果的官方文档可以看到其实它是一个互斥锁。从底层来看,等待os_unfair_lock锁的线程会处于休眠状态,也不是忙等状态。

#import <os/lock.h>
//静态初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&lock);
//尝试加锁,加锁失败返回NO,成功返回YES
bool isCanLock = os_unfair_lock_trylock(&lock);
//安全操作部分
    {}
//解锁
os_unfair_lock_unlock(&lock);

3. dispatch_semaphore 信号量

信号量(semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。

实现原理:不是使用忙等,而是阻塞线程并睡眠,主动让出时间片,需要进行上下文切换。
相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。在没有等待情况出现时, 它的性能比pthread_mutex还要高,但一旦有等待情况出现时,性能就会下降许多。

使用也是非常的简单又方便,会用下面几个函数就可以:

//创建锁  value必须 >=0,若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_create(long value);
//可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//可以理解为 unlock,会使得 signal 值 +1
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

dispatch_semaphore_create(long value);和GCD的group等用法一致,这个函数是创建一个dispatch_semaphore类型的信号量,并且创建的时候需要指定信号量的大小。
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 等待信号量。如果信号量值为0,那么该函数就会一直等待,也就是不返回(相当于阻塞当前线程),直到该函数等待的信号量的值大于等于1,该函数会对信号量的值进行减1操作,然后返回。
dispatch_semaphore_signal(dispatch_semaphore_t deem);发送信号量。该函数会对信号量的值进行加1操作。

  通常等待信号量和发送信号量的函数是成对出现的。并发执行任务时候,在当前任务执行之前,用dispatch_semaphore_wait函数进行等待(阻塞),直到上一个任务执行完毕后且通过dispatch_semaphore_signal函数发送信号量(使信号量的值加1),dispatch_semaphore_wait函数收到信号量之后判断信号量的值大于等于1,会再对信号量的值减1,然后当前任务可以执行,执行完毕当前任务后,再通过dispatch_semaphore_signal函数发送信号量(使信号量的值加1),通知执行下一个任务......如此一来,通过信号量,就达到了并发队列中的任务同步执行的要求。

例如:

- (void)dispatchSemaphore{
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    //等待时间
    dispatch_time_t overTime = DISPATCH_TIME_FOREVER;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //等同加锁 -1
        dispatch_semaphore_wait(signal, overTime);
        NSLog(@"线程1  执行任务");
        sleep(3);
        //发通知  等同解锁  +1
        dispatch_semaphore_signal(signal);
        NSLog(@"线程1  解锁");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //等同加锁 -1
        dispatch_semaphore_wait(signal, overTime);
        NSLog(@"线程2 执行任务");
        //发通知  等同解锁  +1
        dispatch_semaphore_signal(signal);
        NSLog(@"线程2  解锁");
    });
}

4. pthread_mutex 跨平台的锁 ,互斥锁

pthread_mutexc底层的线程锁,关于pthread的各种同步机制可以看pthread的各种同步机制

pthread_mutex互斥锁 实现原理:不是使用忙等,而是阻塞线程并睡眠,主动让出时间片,需要进行上下文切换。

初始化的时候创建属性可以选择锁类型:

PTHREAD_MUTEX_NORMAL //普通锁
PTHREAD_MUTEX_RECURSIVE //递归锁,用于递归调用的时候避免产生死锁情况。

使用需导入头文件:#import <pthread.h>

//定义pthreadmutex锁的属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
/*
  PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。
  PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。
  PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。
  PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
*/
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);//定义锁的属性
// 初始化(两种)
//1.普通初始化
//创建锁
static pthread_mutex_t pLock;
//初始化一个互斥锁
pthread_mutex_init(&pLock, &attr);
//销毁,一定销毁对应的属性。
pthread_mutexattr_destroy(&attr);
    
//另一种创建锁的方式,不需要设置pthread_mutexattr_t属性的时候
//2.宏初始化
//static pthread_mutex_t pLock = PTHREAD_MUTEX_INITIALIZER;
    
//申请锁
pthread_mutex_lock(&pLock);
//安全操作部分
    {}
//释放锁
pthread_mutex_unlock(&pLock);
//销毁
pthread_mutex_destroy(&pLock);   

5. pthread_cond_t 条件锁

pthread_mutex 还可以创建条件锁,提供了和 NSCondition 一样的条件控制,初始化互斥锁同时使用pthread_cond_init来初始化条件数据结构

//条件锁
pthread_cond_t cond;
//静态初始化
//pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
pthread_condattr_t condAttr;
pthread_mutex_t mutex;
const struct timespec abstimel;
// 初始化
pthread_cond_init(&cond, &condAttr);
//1.放开当前锁 2.使当前线程进入休眠(wait) 3.唤醒后会再次mutex程加锁
pthread_cond_wait(&cond, &mutex);
//在time之前等待,之后放开锁。
pthread_cond_timedwait(&cond, &mutex, &abstimel);
// 唤醒一个被wait的线程
pthread_cond_signal(&cond);
// 唤醒所有被wait的线程
pthread_cond_broadcast(&cond);
// 销毁
pthread_cond_destroy(&cond);

6. pthread_rwlock_t 读写锁

读写锁,(互斥锁的进化)分为读锁(rlock)和写锁(wlock),可以有多个线程共同持有读锁,但是写锁只能有一个线程持有。

  • 当读写锁被一个线程以读模式占用的时候,写操作的其他线程会被阻塞,读操作的其他线程还可以继续进行。
  • 当读写锁被一个线程以写模式占用的时候,写操作的其他线程会被阻塞,读操作的其他线程也被阻塞。
//静态初始化读锁
//pthread_rwlock_t rLock = PTHREAD_RWLOCK_INITIALIZER;

pthread_rwlockattr_t rwlock_attr;
pthread_rwlock_t rwlock;
//动态初始化
pthread_rwlockattr_init(&rwlock_attr);
pthread_rwlock_init(&rwlock, &rwlock_attr);

//获取一个读出锁
pthread_rwlock_rdlock(&rwlock);  
pthread_rwlock_tryrdlock(&rwlock);
//获取一个写入锁
pthread_rwlock_wrlock(&rwlock); 
pthread_rwlock_trywrlock(&rwlock);
//释放一个写入锁或者读出锁
pthread_rwlock_unlock(&rwlock); 

比如:

__block pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
    
//读
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
    //NSLog(@"线程0:随眠 1 秒");//还是不打印能直观些
    sleep(1);
    NSLog(@"线程0:加锁");
    pthread_rwlock_rdlock(&rwlock);
    NSLog(@"线程0:读");
    pthread_rwlock_unlock(&rwlock);
    NSLog(@"线程0:解锁");
});
    
//写
dispatch_sync(dispatch_get_global_queue(0, 0),^{       
    //NSLog(@"线程1:随眠 3 秒");
    sleep(3);      
    NSLog(@"线程1:加锁");
    pthread_rwlock_wrlock(&rwlock);
    NSLog(@"线程1:写");
    pthread_rwlock_unlock(&rwlock);
    NSLog(@"线程1:解锁");
});

7. NSLock、NSCondition、NSConditionLock、NSRecursiveLock

简介: 都属于互斥锁。
NSLock 底层是对 pthread_mutex_t 的封装.对应的参数是 PTHREAD_MUTEX_NORMAL
NSCondition 底层则是对 pthread_cond_t 的封装.
NSConditionLock 的底层则是使 NSCondition 实现的.
NSRecursiveLock 则是对 pthread_mutex_tPTHREAD_MUTEX_RECURSIVE 参数的封装。
实现原理可以通过 GNUstep 查看
以上都是苹果对pthread_mutex的封装,让锁的使用更面向对象了。都遵守NSCopying协议,此协议中提供了加锁和解锁方法。

  • NSLock 普通对象锁

实质:NSLock 只是在内部封装了一个 pthread_mutex,属性为 PTHREAD_MUTEX_ERRORCHECK,它会损失一定性能换来错误提示。

//加锁
- (void)lock;  
//解锁
- (void)unlock; 
//能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
- (BOOL)tryLock;  
//这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO
- (BOOL)lockBeforeDate:(NSDate *)limit;  

例如:

- (void)NSLockDemo{
    // 初始化锁
    NSLock *lock = [[NSLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 尝试加锁ing...");
        [lock lock]; // 加锁,也有tryLock方法
        sleep(3);//睡眠3秒
        NSLog(@"执行线程1");
        [lock unlock];// 解锁
        NSLog(@"线程1解锁成功");
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 尝试加锁ing...");
        BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];//超时时间之前不能获得锁就返回NO
        if (x) {
            NSLog(@"执行线程2");
            [lock unlock];
            NSLog(@"线程2解锁成功");
        }else{
            NSLog(@"线程2加锁失败");
        }
    });
}
  • NSCondition 条件锁

条件锁,也是对mutepthread_cond_t的封装。可以调用wait 方法,等待条件成立再执行锁一下的内容。signle方法唤醒线程锁。

- (void)wait;   // 线程等待
- (BOOL)waitUntilDate:(NSDate *)limit;  // 设置线程等待时间,过了这个时间就会自动执行后面的代码
- (void)signal; // 唤醒一个设置为wait等待的线程
- (void)broadcast;  // 唤醒所有设置为wait等待的线程,这个锁一般用的较少

例如:

- (void)testConditionLock{
    NSCondition *condition = [[NSCondition alloc] init];
    //线程一
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [condition lock];
        NSLog(@"线程一  加锁成功");
        [condition wait];
        NSLog(@"线程一   执行任务");
        [condition unlock];
        NSLog(@"线程一  解锁");
        [condition signal];
    });
    
    //线程二
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [condition lock];
        NSLog(@"线程二  加锁成功");
        [condition wait];
        NSLog(@"线程二   执行任务");
        [condition unlock];
        NSLog(@"线程二  解锁");
        [condition signal];
        
    });
    
    //线程三
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [condition lock];
        NSLog(@"线程三  加锁成功");
        //[condition wait];
        NSLog(@"线程三   执行任务");
        [condition unlock];
        NSLog(@"线程三  解锁");
        [condition signal];
    });
}

结果为:

2019-03-10 12:09:02.920733+0800 多线程测试[11562:21367582] 线程一  加锁成功
2019-03-10 12:09:02.921037+0800 多线程测试[11562:21367580] 线程二  加锁成功
2019-03-10 12:09:02.921173+0800 多线程测试[11562:21367581] 线程三  加锁成功
2019-03-10 12:09:02.921280+0800 多线程测试[11562:21367581] 线程三   执行任务
2019-03-10 12:09:02.921397+0800 多线程测试[11562:21367581] 线程三  解锁
2019-03-10 12:09:02.921551+0800 多线程测试[11562:21367582] 线程一   执行任务
2019-03-10 12:09:02.921651+0800 多线程测试[11562:21367582] 线程一  解锁
2019-03-10 12:09:02.921787+0800 多线程测试[11562:21367580] 线程二   执行任务
2019-03-10 12:09:02.921933+0800 多线程测试[11562:21367580] 线程二  解锁

从中可以看出来NSCondition 缺点:不能保证任务有序执行,只能确保执行的任务。

  • NSConditionLock 条件锁

是对NSCondition进一步封装,一个线程获得了锁,其它线程等待。

//初始化锁时,指定一个默认的条件
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
//condition与内部相同才会获取锁对象并立即返回,否则阻塞线程直到condition相同
- (void)lockWhenCondition:(NSInteger)condition;
//满足特定条件Condition,尝试着加锁,返回bool
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
//解锁成功,并且设置lock.condition = condition
- (void)unlockWithCondition:(NSInteger)condition;

例如:

- (void)NSConditionLockDemo{
    //初始化,指定条件为0
    NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //如果条件为0则可以加上锁
        if([cLock tryLockWhenCondition:0]){
            NSLog(@"线程1");
            //解锁并且指定条件为1
            [cLock unlockWithCondition:1];
        }else{
            NSLog(@"失败");
        }
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //如果条件为3则可以加上锁
        [cLock lockWhenCondition:3];
        NSLog(@"线程2");
        [cLock unlockWithCondition:2];
    });
    
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lockWhenCondition:1];
        NSLog(@"线程3");
        [cLock unlockWithCondition:3];
    });
}

结果为:

2019-03-10 18:47:54.994139+0800 多线程测试[18570:21989216] 线程1
2019-03-10 18:47:54.994973+0800 多线程测试[18570:21989215] 线程3
2019-03-10 18:47:54.995135+0800 多线程测试[18570:21989214] 线程2
  • NSRecursiveLock 递归锁

有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁。
例如:

- (void)recursiveLockTest {
    //创建锁
    NSLock *lock = [[NSLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            NSLog(@"加锁%d",value);
            [lock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"休眠%d",value);
                value--;
                TestMethod(value);
            }
            NSLog(@"解锁%d",value);
            [lock unlock];
        };
        TestMethod(5);
        NSLog(@"结束");
    });
}

我们发现"结束" 永远不会被打印出来,其实lock 先锁上了,但未执行解锁的时候,就会进入递归的下一层,而再次请求上锁,阻塞了该线程,线程被阻塞了,自然后面的解锁代码不会执行,而形成了死锁,这个时候可以使用递归锁来解决。
它和 NSLock 的区别在于,使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
例如:

- (void)NSRecursiveLockTest {
    //创建锁
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        static void(^TestMethod)(int);
        TestMethod = ^(int value)
        {
            NSLog(@"加锁%d",value);
            [recursiveLock lock];
            if (value > 0)
            {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"休眠%d",value);
                value--;
                TestMethod(value);
            }
            NSLog(@"解锁%d",value);
            [recursiveLock unlock];
        };
        TestMethod(5);
        NSLog(@"结束");
    });
}

运行结果为:

2019-02-22 19:10:39.534716+0800 多线程测试[18849:22028091] 加锁5
2019-03-10 19:10:40.537983+0800 多线程测试[18849:22028091] 休眠5
2019-03-10 19:10:40.538272+0800 多线程测试[18849:22028091] 加锁4
2019-03-10 19:10:41.543193+0800 多线程测试[18849:22028091] 休眠4
2019-03-10 19:10:41.543514+0800 多线程测试[18849:22028091] 加锁3
2019-03-10 19:10:42.544768+0800 多线程测试[18849:22028091] 休眠3
2019-03-10 19:10:42.545114+0800 多线程测试[18849:22028091] 加锁2
2019-03-10 19:10:43.545918+0800 多线程测试[18849:22028091] 休眠2
2019-03-10 19:10:43.546192+0800 多线程测试[18849:22028091] 加锁1
2019-03-1019:10:44.547666+0800 多线程测试[18849:22028091] 休眠1
2019-03-10 19:10:44.547927+0800 多线程测试[18849:22028091] 加锁0
2019-03-10 19:10:44.548097+0800 多线程测试[18849:22028091] 解锁0
2019-03-10 19:10:44.548250+0800 多线程测试[18849:22028091] 解锁0
2019-03-10 19:10:44.548458+0800 多线程测试[18849:22028091] 解锁1
2019-03-10 19:10:44.548648+0800 多线程测试[18849:22028091] 解锁2
2019-03-10 19:10:44.548850+0800 多线程测试[18849:22028091] 解锁3
2019-03-10 19:10:44.549040+0800 多线程测试[18849:22028091] 解锁4
2019-03-10 19:10:44.549231+0800 多线程测试[18849:22028091] 结束

下面是对上面介绍的各种锁的执行效率的定性分析(只代表加解锁的效率,比如执行1万次加解锁的操作耗费的时间)


性能图

如上,就是本次介绍的iOS锁的知识。最后以一个小小的总结完成锁的介绍:

  1. 所有的锁基本都是创建锁、加锁、等待、解锁的流程,所以并不复杂。

  2. 如果追求锁的极致性能,可以考虑更偏底层实现的pthread_mutex互斥锁以及信号量的方式。

  3. @synchronized的效率最低,但是它使用最方便,所以如果没有性能瓶颈的话使用它也不错。

  1. 什么情况使用自旋锁比较划算
  • 预计线程等待锁的时间很短
  • 加锁的代码(临界区)会经常调用,但竞争情况很少发生。
  • CPU资源不紧张
  • 多核处理器
  1. 什么情况使用互斥锁比较划算
  • 预计线程等待锁的时间比较长
  • 单核处理器,可以休眠
  • 临界区有IO(文件读取)操作
  • 临界区代码比较复杂,或者循环量大
  • 竞争非常激烈。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,505评论 0 6
  • 补充: 可以看到除了OSSpinLock外,dispatch_semaphore和pthread_mutex性能是...
    笨坨阅读 3,496评论 0 10
  • 线程安全是怎么产生的 常见比如线程内操作了一个线程外的非线程安全变量,这个时候一定要考虑线程安全和同步。 - (v...
    幽城88阅读 647评论 0 0
  • Q:为什么出现多线程? A:为了实现同时干多件事的需求(并发),同时进行着下载和页面UI刷新。对于处理器,为每个线...
    幸福相依阅读 1,570评论 0 2
  • 1. 什么情况下会有线程隐患? 我们在使用多线程技术带来的便利的同时,也需要考虑下多线程所带来的隐患。比如,我们可...
    沉江小鱼阅读 806评论 0 11