ios开发中的几种锁(一)

版本记录

版本号 时间
V1.0 2017.05.20

前言

ios中有好几种锁,比如自旋锁,互斥锁,信号量等等,锁其实是多线程数据安全的一种解决方案,作用就是保证同一时间只有一个线程访问和改变某些敏感数据,这些锁的性能也是差别很大,最近看了几个技术大牛的技术博客,我才发现我以前对锁的理解太肤浅了,心虚的赶紧找资料又开始了深入学习,然后整理出来。
这篇主要讲这几种锁的基本情况。

详情

在说明几种锁的基本情况之前,我们先看看ios开发中这八种锁的名称和它们的性能,如下图所示。

ios开发中的几种锁(一)_第1张图片
几种锁的性能比较

下面主要对这几种锁的使用简单的进行说明。

一、OSSpinLock自旋锁

OSSpinLock自旋锁,它的性能相对是最高的,差不多是150us。下面我们直接上代码。

1. JJOSSLockVC.h
#import 

@interface JJOSSLockVC : UIViewController

@end


2. JJOSSLockVC.m

#import "JJOSSLockVC.h"
#import "libkern/OSAtomic.h"

@interface JJOSSLockVC ()

@end

@implementation JJOSSLockVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //自旋锁
    [self aboutOssPinLock];
    
}

#pragma mark - Object Private Function

//自旋锁
- (void)aboutOssPinLock
{
    __block OSSpinLock osslock = OS_SPINLOCK_INIT;
    NSInteger __block num = 10;
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"线程1%@准备上锁",[NSThread currentThread]);
        
        OSSpinLockLock(&osslock);
        NSLog(@"我是线程1%@",[NSThread currentThread]);
        num = num + 1;
        NSLog(@"num1=%ld",num);
        
        OSSpinLockUnlock(&osslock);
        NSLog(@"我是线程1解锁了");
        
    });
    
    NSLog(@"---------分割线----------");
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"线程2%@准备上锁",[NSThread currentThread]);
        
        OSSpinLockLock(&osslock);
        NSLog(@"我是线程2%@",[NSThread currentThread]);
        num = num - 1;
        NSLog(@"num2=%ld",num);
        
        OSSpinLockUnlock(&osslock);
        NSLog(@"我是线程2解锁了");
        
    });

}

@end

看输出结果

2017-05-20 10:05:35.738 lock[1557:65798] 线程1{number = 3, name = (null)}准备上锁
2017-05-20 10:05:35.738 lock[1557:65630] ---------分割线----------
2017-05-20 10:05:35.739 lock[1557:65798] 我是线程1{number = 3, name = (null)}
2017-05-20 10:05:35.739 lock[1557:65799] 线程2{number = 4, name = (null)}准备上锁
2017-05-20 10:05:35.739 lock[1557:65798] num1=11
2017-05-20 10:05:35.740 lock[1557:65798] 我是线程1解锁了
2017-05-20 10:05:35.740 lock[1557:65799] 我是线程2{number = 4, name = (null)}
2017-05-20 10:05:35.740 lock[1557:65799] num2=10
2017-05-20 10:05:35.740 lock[1557:65799] 我是线程2解锁了

由输出结果可知,num数据被锁住了,不会因为两个线程的访问而导致数据不安全。可以发现当我们同时锁上线程1和线程2的时候,线程2会一直等待(自旋锁不会让等待的进入睡眠状态),直到线程1的任务执行完且解锁完毕,线程2才会执行。

下面我们修改一下代码,将线程1的解锁代码注释掉

//注释掉线程1的解锁代码
// OSSpinLockUnlock(&osslock);

让我们看一下输出结果

2017-05-20 10:27:56.603 lock[1832:82635] ---------分割线----------
2017-05-20 10:27:56.603 lock[1832:82692] 线程1{number = 3, name = (null)}准备上锁
2017-05-20 10:27:56.604 lock[1832:82692] 我是线程1{number = 3, name = (null)}
2017-05-20 10:27:56.604 lock[1832:82693] 线程2{number = 4, name = (null)}准备上锁
2017-05-20 10:27:56.604 lock[1832:82692] num1=11
2017-05-20 10:27:56.604 lock[1832:82692] 我是线程1解锁了

由输出结果可知,因为我们注释掉了线程1中的解锁代码,会绕过线程1,直到调用了线程2的解锁方法才会继续执行线程1中的任务,正常情况下,lock和unlock最好成对出现。这里注释掉了线程1的解锁代码,导致线程1无法解锁,所以线程2里面的num2不会执行和打印输出。

这里面用到了几个参数,如下所示:

OS_SPINLOCK_INIT: 默认值为 0,在 locked 状态时就会大于 0,unlocked状态下为 0
OSSpinLockLock(&oslock):上锁,参数为 OSSpinLock 地址
OSSpinLockUnlock(&oslock):解锁,参数为 OSSpinLock 地址
OSSpinLockTry(&oslock):尝试加锁,可以加锁则立即加锁并返回 YES,反之返回 NO

这里还要说一下trylock和lock的区别,如下所示:

  • 当前线程锁失败,也可以继续其它任务,用 trylock 合适。
  • 当前线程只有锁成功后,才会做一些有意义的工作,那就 lock,没必要轮询 trylock。

二、dispatch_semaphore 信号量

还是直接看代码


1. JJSemaphoreLockVC.h
#import 

@interface JJSemaphoreLockVC : UIViewController

@end

2. JJSemaphoreLockVC.m

#import "JJSemaphoreLockVC.h"

@interface JJSemaphoreLockVC ()

@end

#pragma mark - Override Base Function

@implementation JJSemaphoreLockVC

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self aboutSemaphoreLock];
}

#pragma mark - Object Private Function

- (void)aboutSemaphoreLock
{
    //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);
    NSInteger __block num = 10;
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"我是线程1%@,等待中",[NSThread currentThread]);
        
        //signal值 -1
        dispatch_semaphore_wait(signal, overTime);
        num = num + 1;
        NSLog(@"num1=%ld",num);
        
        //signal值 +1
        dispatch_semaphore_signal(signal);
        NSLog(@"线程1   发送信号");
        
    });
    
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSLog(@"我是线程2%@,等待中",[NSThread currentThread]);
        
        //signal值 -1
        dispatch_semaphore_wait(signal, overTime);
        num = num + 1;
        NSLog(@"num2=%ld",num);
        
        //signal值 +1
        dispatch_semaphore_signal(signal);
        NSLog(@"线程2  发送信号");
        
    });
}

@end

下面看输出结果

2017-05-20 11:02:49.473 lock[2238:107199] 我是线程2{number = 4, name = (null)},等待中
2017-05-20 11:02:49.473 lock[2238:107200] 我是线程1{number = 3, name = (null)},等待中
2017-05-20 11:02:49.474 lock[2238:107199] num2=11
2017-05-20 11:02:49.476 lock[2238:107199] 线程2  发送信号
2017-05-20 11:02:49.476 lock[2238:107200] num1=12
2017-05-20 11:02:49.478 lock[2238:107200] 线程1   发送信号

由上发现,因为我们初始化信号量的时候是大于 0 的,所以并没有阻塞线程,而是直接执行了线程1和线程2。

下面说一下信号量的几个参数

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

下面有个比较形象的比喻,是我在别的博客上看到的,写的不错。

停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值(signal): 它就相当于剩余车位的数目,dispatch_semaphore_wait 函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait 剩余车位就减少一个;当剩余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。

我们再次修改代码

//dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_semaphore_t signal = dispatch_semaphore_create(0);

我们在看一下输出结果

2017-05-20 11:14:49.192 lock[2422:116184] 我是线程2{number = 4, name = (null)},等待中
2017-05-20 11:14:49.192 lock[2422:116201] 我是线程1{number = 3, name = (null)},等待中
2017-05-20 11:14:52.267 lock[2422:116201] num1=11
2017-05-20 11:14:52.267 lock[2422:116184] num2=12
2017-05-20 11:14:52.267 lock[2422:116201] 线程1   发送信号
2017-05-20 11:14:52.268 lock[2422:116184] 线程2  发送信号

这个主要是看时间戳,可以看见49~52,也就是说dispatch_semaphore_create(0)时,线程1和2里面的代码输出num1和num2的值要等3s才会执行,而不会立即执行。

相关参考技术博客

1.iOS 开发中的八种锁(Lock)
2.不再安全的 OSSpinLock
3. NSRecursiveLock递归锁的使用
4.关于dispatch_semaphore的使用
5.实现锁的多种方式和锁的高级用法

后记

今天就写了2种锁,剩下的待续~~~

ios开发中的几种锁(一)_第2张图片
家乡

你可能感兴趣的:(ios开发中的几种锁(一))