(杂记)递归和锁

358 阅读3分钟

故事

老师为了得到一列学生的名字,可以对自己的学生这么说: 我会发给第一个同学一张,第一个同学把纸向后传,每个通过学都重复上一个同学的动作,直到传到最后一名学生。最后一名学生写名字往回传,每个同学再重复上一个同学的动作,直到第一个同学

这就是一个相对完整的递归

对应关系

纸 ------------------ 公共资源

第一个同学 --------- 入口

向后传 ------------- 回调

最后一个同学 ------- 出口

写名字 ------------- 对于公共资源的操作

往回传 ------------- 返回

递归的特征

  1. 递归的入口也是出口:

    是从第一个人接手的,也是最后从第一个人送回的。

  2. 自己回调自己:

    除了最后一名学生,每个人都是在重复上一个人的动作。

  3. 出口条件:

    直到传到最后一名学生才会结束向后传

  4. 公共资源:

    一般情况下递归会轮番对一个公共资源进行传递,直到传递到出口条件,然后再反向传递回来。

为什么递归不能用互斥锁(NSLock)?

  1. 互斥锁在本故事中可以理解为:我没有在这个上写下名字之前谁都不准写。
  2. 互斥锁从第一个人就开始给这张加锁,所以到了最后一名同学要开始写名字的时候已经加了一堆锁了。但是最后一名同学不在上写完名字又不能传回去。这就产生了悖论,也就是死锁。

递归锁(NSRecursiveLock)的工作描述

递归锁本身相当于一个令牌,有了这个令牌我们就能在纸上写东西,然后这个令牌每传一次可使用次数加以,传到最后一个同学后开始使用令牌,每使用一次,可使用次数减一。当然只有拿到过这个令牌的人才有资格用他。

多列(Thread)同时使用一个递归锁(NSRecursiveLock)的情况

如果多列(线程)同时使用一个递归锁(令牌)则照样会导致死锁,不用多解释,你就想想一下好几列(线程)的人一起抢一个令牌啥场面?为啥会造成死锁也能想象出来了吧。

更加安全,更加厚重的递归锁(synchronized)

这时候如果还能再令牌上不仅标识使用次数,还把之前已经传递了哪些同学,这些同学属于那列(Thread)。也就不会出现问题了。但是这样令牌也会变得厚重,而且效率降低。但是换来的却是更加的安全。

代码部分

如果是简单的防死锁用NSLock

void code_testLock(void) {
    
    __block int a = 0;
    NSLock *lock = [[NSLock alloc] init];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [lock lock];
        a++;
        [lock unlock];
    });
}

如果是递归方法则需要用NSRecursiveLock

void code_testLock(void) {
    
dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            addMethod = ^(int a){
                [lock lock];
                if (a < 10) {
                    NSLog(@"%d", a);
                    addMethod(a+1);
                }
                [lock unlock];
            };
            addMethod(0);
        });
}

但是如果这么执行就会死锁

代码1

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);
        });
}

但是区别于下面的方式,下面的方法并不会造成死锁

    for (int i = 0; i < 1000; i++) {
            addMethod = ^(int a){
                dispatch_async(dispatch_get_global_queue(0, 0), ^{
                    [lock lock];
                    if (a < 10) {
                        NSLog(@"%d", a);
                        addMethod(a+1);
                    }
                    [lock unlock];
                });
            };
            addMethod(0);
        }

这时候就要用synchronized

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

源码部分: juejin.cn/post/684490…