iOS-几种锁的应用

前言

这篇文章,记录几种的简单应用。

@synchronized

使用起来最简单的一个锁,直接将要锁定的代码用@synchronized包裹,如下:

- (void)demo33
{
    for (int i = 0; i < 100000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self) {
                self.testArray = [NSMutableArray array];
            }
        });
    }
}

需要注意的是:

  1. @synchronized的参数在使用期间不能为nil
  2. 性能问题
NSLock

NSLock 互斥锁的一种,性能高于@synchronized,使用也比较简单

- (void)demo33
{
    NSLock *lock = [[NSLock alloc]init];
    for (int i = 0; i < 100000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [lock lock];
            self.testArray = [NSMutableArray array];
            [lock unlock];
        });
    }
}

注意,当存在嵌套递归的情况时,比如下面的代码:

NSLock *lock = [[NSLock alloc] init];
for (int i= 0; i<100; i++) {
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                [lock lock];
                if (value > 0) {
                  NSLog(@"current value = %d",value);
                  testMethod(value - 1);
                }
                [lock unlock];
            };
            testMethod(10);
        });
    }

这里执行时会发现有问题了,当在嵌套递归的情况下,单个线程内的lock会发生死锁;而线程与线程之间发生循环等待任务;从而导致无法正常执行下去,那么接下看另外一个锁。

NSRecursiveLock

从类名上看,NSRecursiveLock 是一个递归锁,我们用上面的案例,然后替换使用NSRecursiveLock看下:

- (void)demo44
{
    NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    for (int i= 0; i<100; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                [recursiveLock lock];
                if (value > 0) {
                  NSLog(@"current value = %d",value);
                  testMethod(value - 1);
                }
                [recursiveLock unlock];
            };
            testMethod(10);
        });
    }
}

执行后发现:

2020-11-10 17:39:09.416482+0800 TestApp[47016:8556099] current value = 10
2020-11-10 17:39:09.416671+0800 TestApp[47016:8556099] current value = 9
2020-11-10 17:39:09.416811+0800 TestApp[47016:8556099] current value = 8
2020-11-10 17:39:09.416952+0800 TestApp[47016:8556099] current value = 7
2020-11-10 17:39:09.417089+0800 TestApp[47016:8556099] current value = 6
2020-11-10 17:39:09.417214+0800 TestApp[47016:8556099] current value = 5
2020-11-10 17:39:09.417338+0800 TestApp[47016:8556099] current value = 4
2020-11-10 17:39:09.417461+0800 TestApp[47016:8556099] current value = 3
2020-11-10 17:39:09.417590+0800 TestApp[47016:8556099] current value = 2
2020-11-10 17:39:09.417858+0800 TestApp[47016:8556099] current value = 1

截屏2020-11-10 下午5.40.03.png

这里由于我们的[recursiveLock lock];位置不对,导致线程之间发生循环等待,从而导致线程间的死锁,我们调整下lock的位置:

- (void)demo44
{
  NSRecursiveLock *recursiveLock = [[NSRecursiveLock alloc] init];
    for (int i= 0; i<100; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            [recursiveLock lock];
            testMethod = ^(int value){
                if (value > 0) {
                  NSLog(@"current value = %d",value);
                  testMethod(value - 1);
                }
                [recursiveLock unlock];
            };
            testMethod(10);
        });
    }
}

然后执行结果正常。这里也就说明一个坑点就是,尽管NSRecursiveLock是一把支持递归嵌套场景的锁,但是如果使用不当还是会出现各种问题,所以这里的建议是如果业务逻辑简单,直接使用@synchronized锁(使用简单,支持多线程递归嵌套);如果要使用NSRecursiveLock(性能高),就要注意使用的细节了。

NSCondition

iOS下一个简单的条件锁,当进程的某些资源要求不满足时就进入休眠,也就
是锁住了。当资源被分配到了,条件锁打开,进程继续运行。这里只做一些简单的使用。条件锁即生产者和消费者的关系,比如卖包子和买包子,当前卖家有包子可卖,买包子的就可以正常买;当包子卖完时,此时卖家暂停卖包子,开始蒸包子,买家也暂停买包子,开始等待;等卖家包子蒸好了,卖家通知买家可以买了,买家就能继续买包子了。😁
来看一个案例:

- (void)lg_testConditon{
    
    _testCondition = [[NSCondition alloc] init];   //条件锁
    //创建生产-消费者
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self producer];  //蒸包子
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self consumer]; //买包子
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self consumer]; //买包子
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self producer]; //蒸包子
        });
    }
}
- (void)producer{
    [_testCondition lock]; // 操作的多线程影响
    self.bunCount = self.bunCount + 1;
    NSLog(@"蒸好一个 现有 count %zd",self.bunCount);
    [_testCondition signal]; // 信号,卖家说可以买了
    [_testCondition unlock];
}
- (void)consumer{
     [_testCondition lock];  // 操作的多线程影响
    if (self.bunCount == 0) { //没包子了 买家进入等待(即等待signal信号)
        NSLog(@"等待 count %zd",self.bunCount);
        [_testCondition wait];
    }
    //注意消费行为,要在等待条件判断之后 开始买
    self.bunCount -= 1;
    NSLog(@"消费一个 还剩 count %zd ",self.bunCount);
    [_testCondition unlock];
}

这里要注意的是由于多线程,这里无论是买还是卖都要加锁(非常贴进生活嘛),由于每次lock,unlock,signal,wait显得非常的繁琐,所以就衍生出了一个更加高级点的条件锁NSConditionLock

NSConditionLock

这个锁本质上跟NSCondition是一致的,只是将之前的繁琐的操作给去掉了,对开发者更加的友好,通过一个condition来控制执行。

#pragma mark -- NSConditionLock
- (void)testConditonLock{
    // 信号量
    NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
         [conditionLock lockWhenCondition:1];  //if condition == 1 执行
        NSLog(@"任务 1");
         [conditionLock unlockWithCondition:0];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
       
        [conditionLock lockWhenCondition:2]; // if condition == 2 执行
        sleep(0.1);
        NSLog(@"任务 2");
        [conditionLock unlockWithCondition:1];  // condition == 1
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];     // 没有条件直接执行
       NSLog(@"任务 3");     
       [conditionLock unlock];
    });
}

这里其实就是在加锁的时候给了解锁条件,NSConditionLock在初始化时给了一个默认的条件:condition = 2, 这里的任务2满足条件,会执行;任务3不需要条件,也会执行;由于异步并发的原因,任务2和任务3的执行时没有顺序的,但任务1的执行就依赖于任务2,任务1 只有满足condition == 1的时候才能执行,而这个需要等待任务2执行完后,调用[conditionLock unlockWithCondition:1];condition == 1,然后发出broadcast通知,让满足条件的任务去执行。

dispatch_semaphore_t

dispatch_semaphore_tGCD下的信号量锁,这个锁还是比较常用的,特别是在一些异步取值同步返回的操作中,比如:

- (int)demo777
{
    __block int result = 0;
    dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        result = 1+0+2+4;
        dispatch_semaphore_signal(semphore); //释放等待 value++
    });
    //进入等待 value--
    dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
    NSLog(@"result == %d",result);
    return result;
}

value小于0时进入等待,即dispatch_semaphore_wait()会做value--操作;
dispatch_semaphore_signal()会做value++操作。主要在使用时,signalwait是成对儿出现的。

总结

关于NSLockNSCondition,NSConditionLock 都归属于Foundation框架内部,由于OC的Foundation没有开源,如果想进一步了解其内部实现,可以参考swift-Foundation

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

推荐阅读更多精彩内容

  • 锁是最常用的同步工具。一段代码段在同一个时间只能允许被有限个线程访问,比如一个线程 A 进入需要保护代码之前添加简...
    没八阿哥的程序阅读 778评论 0 0
  • 前言 对于iOS中各种锁的学习总结,供日后查阅 引子 日常开发中,@property (nonatomic, st...
    Tr2e阅读 875评论 1 1
  • 抛砖引玉 说到锁不得不提线程安全,说到线程安全,作为iOS程序员又不得不提 nonatomic 与 atomic ...
    Inlight先森阅读 2,035评论 0 23
  • 只要系统中存在多线程,存在共享资源,那么锁就是一个绕不过去的概念,像后台数据库读写数据就要用到读写锁,来保证数据的...
    忧郁的小码仔阅读 893评论 0 1
  • 可以看这篇文章:iOS 常见知识点(三):Lock 什么是锁 锁是一种同步机制,用于在存在多线程的环境中实施对资源...
    lxl125z阅读 402评论 0 0