ios开发中的几种锁(一)

版本记录

版本号 时间
V1.0 2017.05.20

前言

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

详情

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

几种锁的性能比较

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

一、OSSpinLock自旋锁

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

1. JJOSSLockVC.h
#import <UIKit/UIKit.h>

@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<NSThread: 0x60000007bb80>{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<NSThread: 0x60000007bb80>{number = 3, name = (null)}
2017-05-20 10:05:35.739 lock[1557:65799] 线程2<NSThread: 0x608000076400>{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<NSThread: 0x608000076400>{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<NSThread: 0x608000074240>{number = 3, name = (null)}准备上锁
2017-05-20 10:27:56.604 lock[1832:82692] 我是线程1<NSThread: 0x608000074240>{number = 3, name = (null)}
2017-05-20 10:27:56.604 lock[1832:82693] 线程2<NSThread: 0x608000074000>{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 <UIKit/UIKit.h>

@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<NSThread: 0x60000026c5c0>{number = 4, name = (null)},等待中
2017-05-20 11:02:49.473 lock[2238:107200] 我是线程1<NSThread: 0x6080002640c0>{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<NSThread: 0x60800006ef40>{number = 4, name = (null)},等待中
2017-05-20 11:14:49.192 lock[2422:116201] 我是线程1<NSThread: 0x60800006f000>{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种锁,剩下的待续~~~

家乡
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,516评论 0 6
  • 为什么要有锁? 在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就...
    153037c65b0c阅读 550评论 0 1
  • 一、线程锁相关概念 线程锁:我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全...
    2525252472阅读 403评论 0 2
  • 自旋锁和互斥锁 共同点:都能保证同一时刻只能有一个线程操作锁住的代码。都能保证线程安全。不同点: 互斥锁(mute...
    中轴线_lz阅读 729评论 0 0
  • 许艺涵阅读 183评论 0 0