iOS锁下

上篇文章介绍synchronized锁,今天介绍的是其他常用锁:NSLock,NSRecursiveLock ,NSCondition,NSConditionLock

锁的概念

锁的分类——互斥锁,自旋锁,读写锁

自旋锁
  • 自旋锁是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当它尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

  • 优点:自旋锁不会引起调用者睡眠,所以不会进行线程调度、CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁

  • 缺点:自旋锁一直占用CPU,他在未获得锁的情况下一直运行(自旋)占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低

总结:效率高,但是一直占用CPU耗费资源,不能实现递归调用。

互斥锁

什么是互斥锁呢?

  • 当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,此时CPU可以调度其他线程。当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。
  • 说到互斥后想到同步,同步是只有一个任务执行完了,下个任务才可以执行。

同步:互斥+顺序

常见的互斥锁:@synchronizedNSLockpthread_mutexNSConditionLock(条件锁),NSCondition(条件锁),NSRecursiveLock(递归锁)
以上概念转载自:https://juejin.cn/post/6999520963658268709/

锁的归类

条件锁:就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了,当资源被分配到了,条件锁打开,进程继续运行

  • NSCondition
  • NSConditionLock

递归锁:就是同一个线程可以加锁N次而不会引发死锁

  • NSRecursiveLock
  • pthread_mutext(recursive)

信号量:是一种更高级的同步机制,互斥锁可以说是semaphore在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。

  • dispatch_semaphore

读写锁

读写锁实际是一种特殊的互斥锁,他把对共享资源的访问分为读者和写者,读者只有对共享资源的读权限,写着可以对共享资源做写处理。所以一般我们在读的时候不会去加互斥锁,允许多个读者进行访问,但是如果有遇到写者访问资源,这时候必须对资源进行加锁,等其访问结束后才能继续读/写
特性:

  • 多个读者同时访问
  • 读与写互斥
  • 写和写互斥即单写

上面都是一些概念,接下来就开始详细分析常用的锁

NSLock,NSRecursiveLock

简单的锁,不能够递归嵌套也不能多线程

- (void)testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<10; 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];
            };
            [lock lock];
            testMethod(10);
            [lock unlock];
        });
    }
}

输出:


image.png

这边我们的锁直接加在testMethod(10);等线程这边递归完成后再继续下个线程,但是有时候我们可能会加在递归函数里面,如上面的注释,那就会造成死锁,为什么会造成死锁呢?在testMethod刚进去加锁,但是testMethod还没出来又嵌套进入到testMethod里又加锁,就造成了死锁,解决办法就是使用嵌套锁NSRecursiveLock/synchronized

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

输出结果:
 current value = 10 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 9 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 8 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 7 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 6 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 5 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
current value = 4 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 3 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 2 -- <NSThread: 0x281bff740>{number = 3, name = (null)}
 current value = 1 -- <NSThread: 0x281bff740>{number = 3, name = (null)}

这边使用NSRecursiveLock尝试解决,但是很遗憾还是崩,只是崩溃的不一样,最后输出的结果如上图,为什么呢?因为NSRecursiveLock不支持多线程,如果只开一个线程就不会崩,但是换成synchronized就OK

- (void)testRecursive{
    NSLock *lock = [[NSLock alloc] init];
    for (int i= 0; i<2; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            static void (^testMethod)(int);
            testMethod = ^(int value){
                @synchronized (self) {
                    if (value > 0) {
                        NSLog(@"current value = %d -- %@",value, [NSThread currentThread]);
                        testMethod(value - 1);
                    }
                }
            };
            testMethod(10);
        });
    }
}
输出结果:
current value = 10 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 9 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 8 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 7 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 6 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 5 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 4 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 3 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 2 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 1 -- <NSThread: 0x2809fff40>{number = 4, name = (null)}
current value = 10 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 9 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 8 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 7 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 6 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 5 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 4 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 3 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 2 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}
current value = 1 -- <NSThread: 0x2809fd1c0>{number = 5, name = (null)}

接下来我们来看看NSLockNSRecursiveLock的源码,因为NSLockNSRecursiveLock是在Foundation框架,这个是不开源的但是swiftFoundation框架是开源的,我们可以看看swiftFoundation框架

image.png

通过源码就知道NSLock就是对mutex的封装
image.png

这个也是对mutex的封装,但是在初始化跟NSlock有所不同,pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))添加了嵌套属性

NSCondition和NSConditionLock

NSCondition条件锁主要运用在生产着消费者模型

- (void)testConditon{
    
    _testCondition = [[NSCondition alloc] init];
    //创建生产-消费者
    for (int i = 0; i < 50; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_consumer];
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            [self lg_producer];
        });
    }
}

- (void)producer{
    [_testCondition lock]; // 操作的多线程影响
    self.ticketCount = self.ticketCount + 1;
    NSLog(@"生产一个 现有 count %zd",self.ticketCount);
    [_testCondition signal]; // 信号
    [_testCondition unlock];
}

- (void)consumer{
 
     [_testCondition lock];  // 操作的多线程影响
    if (self.ticketCount == 0) {
        NSLog(@"等待 count %zd",self.ticketCount);
        [_testCondition wait];
    }
    //注意消费行为,要在等待条件判断之后
    self.ticketCount -= 1;
    NSLog(@"消费一个 还剩 count %zd ",self.ticketCount);
     [_testCondition unlock];
}

当消费者消费到没有物品时就会处于等待,而生产者每生产一个产品都会通知一下消费者,让那些处于等待状态的消费者及时消费。
底层是对cond的封装
NSConditionLock 的使用

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:2];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        [conditionLock lockWhenCondition:1];
        NSLog(@"线程 1");
        [conditionLock unlockWithCondition:0];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        [conditionLock lockWhenCondition:2];
        sleep(0.1);
        NSLog(@"线程 2");
        [conditionLock unlockWithCondition:1];
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [conditionLock lock];
       NSLog(@"线程 3");
       [conditionLock unlock];
    });

输出:
线程 3
线程 2
线程 1

通过lockWhenCondition控制线程的执行顺序,这边预先设置condition为2,因为线程2的条件是[conditionLock lockWhenCondition:2]满足条件会比线程1优先执行,执行完[conditionLock unlockWithCondition:1];设置为1,这样线程1就满足条件,等待cpu调度

读写锁

之前介绍的读写锁,多个线程可以同时对资源进行读操作不用加锁,但是写操作是需要加锁。这样我们就可以通过GCD的栅栏函数来实现

- (id)init{
    self = [super init];
    if (self){
        // 创建一个并发队列:
        self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
        // 创建数据字典:
        self.dataCenterDic = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)getObjc {
    for (int i=0; i<5; i++) {
        dispatch_async(self.concurrent_queue, ^{
            // 多个线程读取数据
            [self objectForKey:@"a"];
        });
    }
}

#pragma mark - 读数据
- (id)objectForKey:(NSString *)key{
    __block id obj;
    // 同步读取指定数据:
    dispatch_sync(self.concurrent_queue, ^{
        obj = [self.dataCenterDic objectForKey:key];
    });
    return obj;
}

#pragma mark - 写数据
- (void)setObject:(id)obj forKey:(NSString *)key{
    // 异步栅栏调用设置数据:
    dispatch_barrier_async(self.concurrent_queue, ^{
        [self.dataCenterDic setObject:obj forKey:key];
    });
}

注意:同步读取指定数据之所以要加dispatch_sync是为了满足读和写的互斥

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

推荐阅读更多精彩内容