ios开发中的几种锁(三)

版本记录

版本号 时间
V1.0 2017.05.21

前言

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

详情

一、NSLock普通锁

我们先看一下NSLock的API。

@interface NSLock : NSObject <NSLocking> {

@private
    void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@end

先解释一下这几个参数

  • lock、unlock:不多做解释,和上面一样。
  • trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO。
  • lockBeforeDate:这个方法表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回 YES,反之返回 NO。

然后我们看代码


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

@interface JJNSlockVC : UIViewController

@end


2. JJNSlockVC.m
#import "JJNSlockVC.h"
#import <Foundation/Foundation.h>

@interface JJNSlockVC ()

@end

@implementation JJNSlockVC

#pragma mark - Override Base Function

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


#pragma mark - Object Private Function

- (void)aboutNSlock
{
    NSLock *lock = [NSLock new];
    int __block num = 10;
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 尝试加速ing...");
        [lock lock];
        num = num + 1;
        NSLog(@"num1=%d",num);
        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]];
        if (x) {
            NSLog(@"线程2");
            num = num + 1;
            NSLog(@"num2=%d",num);
            [lock unlock];
        }else{
            NSLog(@"失败");
        }
    });

}
@end

看一下运行结果

2017-05-21 18:06:30.005 lock[1726:81709] 线程2 尝试加速ing...
2017-05-21 18:06:30.005 lock[1726:81708] 线程1 尝试加速ing...
2017-05-21 18:06:30.008 lock[1726:81709] 线程2
2017-05-21 18:06:30.009 lock[1726:81709] num2=11
2017-05-21 18:06:30.010 lock[1726:81708] num1=12
2017-05-21 18:06:30.010 lock[1726:81708] 线程1
2017-05-21 18:06:30.011 lock[1726:81708] 线程1解锁成功

二、NSCondition

同上我们还是先看下API

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@end

看一下参数

  • wait:进入等待状态
  • waitUntilDate::让一个线程等待一定的时间
  • signal:唤醒一个等待的线程
  • broadcast:唤醒所有等待的线程

看一下代码

1. 线程延迟3s

1. JJNSConditionVC.h

#import <UIKit/UIKit.h>

@interface JJNSConditionVC : UIViewController

@end

2. JJNSConditionVC.m
#import "JJNSConditionVC.h"
#import <Foundation/Foundation.h>

@interface JJNSConditionVC ()

@end

@implementation JJNSConditionVC

#pragma mark - Override Base Function

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

#pragma mark - Object Private Function

- (void)aboutNSConditionLockWaite3s
{
    NSCondition *cLock = [NSCondition new];
    int __block num = 10;
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"start");
        [cLock lock];
        [cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
        num = num + 1;
        NSLog(@"num1=%d",num);
        NSLog(@"线程1");
        [cLock unlock];
    });
}

@end

下面看一下输出结果

2017-05-21 18:42:08.611 lock[2112:105644] start
2017-05-21 18:42:11.684 lock[2112:105644] num1=11
2017-05-21 18:42:11.685 lock[2112:105644] 线程1

2. 唤醒等待线程

看代码

1. JJNSConditionVC.m

#import "JJNSConditionVC.h"
#import <Foundation/Foundation.h>

@interface JJNSConditionVC ()

@end

@implementation JJNSConditionVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //延迟等待3s
//    [self aboutNSConditionLockWaite3s];
    
    //唤醒一个线程
    [self weakAWaitingThread];
}

#pragma mark - Object Private Function

- (void)weakAWaitingThread
{
    NSCondition *cLock = [NSCondition new];
    int __block num = 10;
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lock];
        NSLog(@"线程1加锁成功");
        [cLock wait];
        NSLog(@"线程1--%@",[NSThread currentThread]);
        num = num + 1;
        NSLog(@"num1=%d",num);
        [cLock unlock];
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lock];
        NSLog(@"线程2加锁成功");
        [cLock wait];
        NSLog(@"线程2--%@",[NSThread currentThread]);
        num = num + 1;
        NSLog(@"num2=%d",num);
        [cLock unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"唤醒一个等待的线程");
        [cLock signal];
    });
}

@end

看输出结果

2017-05-21 18:54:06.471 lock[2294:113847] 线程2加锁成功
2017-05-21 18:54:06.471 lock[2294:113844] 唤醒一个等待的线程
2017-05-21 18:54:06.471 lock[2294:113845] 线程1加锁成功
2017-05-21 18:54:06.472 lock[2294:113847] 线程2--<NSThread: 0x6080002632c0>{number = 3, name = (null)}
2017-05-21 18:54:06.473 lock[2294:113847] num2=11

由上可知,线程2被唤醒了,进行了计算。

3. 唤醒所有线程

看代码


1. JJNSConditionVC.m
#import "JJNSConditionVC.h"
#import <Foundation/Foundation.h>

@interface JJNSConditionVC ()

@end

@implementation JJNSConditionVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //延迟等待3s
//    [self aboutNSConditionLockWaite3s];
    
    //唤醒一个线程
//    [self weakAWaitingThread];
    
    //唤醒所有线程
    [self weakAllThread];
}

#pragma mark - Object Private Function

- (void)weakAllThread
{
    NSCondition *cLock = [NSCondition new];
    int __block num = 10;
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lock];
        NSLog(@"线程1加锁成功");
        [cLock wait];
        NSLog(@"线程1--%@",[NSThread currentThread]);
        num = num + 1;
        NSLog(@"num1=%d",num);
        [cLock unlock];
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cLock lock];
        NSLog(@"线程2加锁成功");
        [cLock wait];
        NSLog(@"线程2--%@",[NSThread currentThread]);
        num = num + 1;
        NSLog(@"num2=%d",num);
        [cLock unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"唤醒所有等待的线程");
        [cLock broadcast];
    });
}

@end

看结果输出

2017-05-21 19:04:11.191 lock[2433:121014] 线程1加锁成功
2017-05-21 19:04:11.191 lock[2433:121017] 唤醒所有等待的线程
2017-05-21 19:04:11.192 lock[2433:121035] 线程2加锁成功
2017-05-21 19:04:11.192 lock[2433:121014] 线程1--<NSThread: 0x600000266400>{number = 3, name = (null)}
2017-05-21 19:04:11.192 lock[2433:121014] num1=11

因为我把三段代码都放在异步执行,这里线程1开始执行并处于等待状态,然后就执行唤醒所有的线程,这个以后线程2才开始进行等待状态,所以这个时候的解锁所有的线程,其实只能解锁线程1的执行,输出结果是正确的。

三、NSLock在递归锁中的错误演示

看代码

#import "JJNSlockVC.h"
#import <Foundation/Foundation.h>

@interface JJNSlockVC ()

@end

@implementation JJNSlockVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
//    [self aboutNSlock];
    
    //NSLock在递归锁中的错误演示
    [self nslockErrorDisplayInRecresive];
}

- (void)nslockErrorDisplayInRecresive
{

    NSLock *nsLock = [[NSLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [nsLock lock];
            if (value > 0) {
                NSLog(@"线程%d", value);
                RecursiveBlock(value - 1);
            }
            [nsLock unlock];
        };
        RecursiveBlock(4);
    });
}

@end

看输出结果

2017-05-21 19:18:58.723 lock[2606:130260] 线程4
2017-05-21 19:18:58.726 lock[2606:130260] *** -[NSLock lock]: deadlock (<NSLock: 0x6000000dbdd0> '(null)')
2017-05-21 19:18:58.727 lock[2606:130260] *** Break on _NSLockError() to debug.

上面这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod 是递归调用的。所以每次进入这个 block 时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。

下面我们将锁换位递归锁,再次尝试

//    NSLock *nsLock = [[NSLock alloc] init];
    NSRecursiveLock * nsLock = [[NSRecursiveLock alloc] init];

让我们再次run起来,查看结果

2017-05-21 19:22:46.774 lock[2669:133395] 线程4
2017-05-21 19:22:46.775 lock[2669:133395] 线程3
2017-05-21 19:22:46.777 lock[2669:133395] 线程2
2017-05-21 19:22:46.777 lock[2669:133395] 线程1

就会发现,线程锁可是递归,而不会锁死了。

相关参考技术博客

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

后记

未完,待续,谢谢大家~~~

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

推荐阅读更多精彩内容

  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,503评论 0 6
  • 线程安全是什么? 当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻...
    6ffd6634d577阅读 2,192评论 1 7
  • 翻译:Synchronization 同步 应用程序中存在多个线程会导致潜在的问题,这些问题可能会导致从多个执行线...
    AlexCorleone阅读 2,448评论 0 4
  • 锁是最常用的同步工具。一段代码段在同一个时间只能允许被有限个线程访问,比如一个线程 A 进入需要保护代码之前添加简...
    没八阿哥的程序阅读 778评论 0 0
  • 自旋锁和互斥锁 共同点:都能保证同一时刻只能有一个线程操作锁住的代码。都能保证线程安全。不同点: 互斥锁(mute...
    中轴线_lz阅读 720评论 0 0