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

你可能感兴趣的:(iOS-几种锁的应用)