美文网首页
iOS 锁的种类和性能

iOS 锁的种类和性能

作者: 懒懒的猫 | 来源:发表于2023-03-03 19:39 被阅读0次

    一、基本概念

    iOS中的锁主要可以分为两大类,互斥锁自旋锁,其他锁都是这两种锁的延伸和扩展。

    1、介绍

    互斥锁:属于sleep-waiting类型的锁,线程A获取到锁,在释放锁之前,其他线程都获取不到锁。互斥锁也分为两种:

    • 递归锁:可重入锁,同一个线程在锁释放前可再次获取锁,即可以递归调用
    • 非递归锁:不可重入,必须等锁释放后才能再次获取锁。
      自旋锁:线程A获取到锁,在释放锁之前,线程B又来获取锁,此时获取不到,线程B就会不断的进入循环,一直检查锁是否已被释放,如果释放,则能获取到锁。

    2、区别

    • 互斥锁:当线程获取锁但没有获取到时,线程会进入休眠状态,等锁被释放时,线程会被唤醒,同时获取到锁,继续执行任务,互斥锁会改变线程的状态。线程从sleep(加锁)—>running(解锁)的过程中,有上下文的切换,cpu的抢占,信号的发送等开销。

    • 自旋锁:当线程获取锁但没获取到时,不会进入休眠,而是一直循环,线程始终处于活跃状态,不会改变线程状态,也就是忙等。线程一直是running(加锁—>解锁),死循环检测锁的标志位。递归调用自旋锁一定会死锁。

    • 对比:互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

    3、使用场景

    • 互斥锁会改变线程的状态,使得内核不断的调度线程资源,因此效率上比自旋锁要低很多,不适合使用自旋锁的场景都使用互斥锁。
    • 自旋锁在线程的等待过程中是活跃的,避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。因此自旋锁适合用于短时间内的轻量级锁定,主要用在临界区持锁时间非常短且CPU资源不紧张的情况下。

    二、iOS 中的锁

    2.1 NSLock

    NSLock「非」递归互斥锁

    NSLocking只定义了加锁(获取锁)-lock,和解锁(释放锁)-unlock两个接口。NSLockNSConditionLockNSRecursiveLockNSCondition都实现了这个协议

    @protocol NSLocking
    
    - (void)lock;
    - (void)unlock;
    
    @end
    
    

    -lock-unlock必须在相同的线程调用,也就是说,他们必须在同一个线程中成对调用,否则会产生未知结果。参考:官方文档原文
    NSLock是使用了pthread_mutex_t封装的互斥锁。

    2.2 NSCondition

    NSCondition也是使用了pthread_mutex_t封装的互斥锁,和NSLock中一模一样,同时还使用了pthread_cond_t。它和NSLock的区别是:

    • NSLock在获取不到锁的时候自动使线程进入休眠,锁被释放后线程又自动被唤醒
    • NSCondition可以使我们更加灵活的控制线程状态,在任何需要的时候使线程进入休眠或唤醒它。
      1.[condition lock]:一般用于多线程同时访问、修改同一个数据源,保证在同一 时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到 unlock ,才可访问
      2.[condition unlock]:与lock 同时使用
      3.[condition wait]:让当前线程处于等待状态
      4.[condition signal]:CPU发信号告诉线程不用在等待,可以继续执行

    2.3 NSConditionLock

    NSConditionLock条件锁就是有特定条件的锁,说白了就是「有条件的互斥锁」。

    • 只读属性condition,保存锁当前的条件(所谓的条件condition就是个NSInteger
    • -lockWhenCondition:获取锁,如果condition与属性相等,则可以获得锁,否则阻塞线程,等待被唤醒
    • -unlockWithCondition:释放锁,并修改condition属性
    // 主线程
        self.conditionLock = [[NSConditionLock alloc] init];
            
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"进入线程1");
            // 当 lock.condition = 2 时,能够获取到锁,否则休眠等待
            [self.conditionLock lockWhenCondition:2];
            NSLog(@"执行任务1");
            sleep(1);
            [self.lock unlock];
            NSLog(@"退出线程1");
        });
            
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"进入线程2");
            [self.conditionLock lockWhenCondition:1];
            NSLog(@"执行任务2");
            sleep(5);
            // 将 lock.condition 修改为2,线程1就能获得锁了
            [self.conditionLock unlockWithCondition:2];
            NSLog(@"退出线程2");
        });
            
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"进入线程3");
            [self.lock lock];
            NSLog(@"执行任务3");
            sleep(2);
            // 将 lock.condition 修改为1,线程2就能获得锁了
            [self.conditionLock unlockWithCondition:1];
            NSLog(@"退出线程3");
        });
    

    结果

    2020-04-24 16:20:44.901816+0800 lock[48829:11985930] 进入线程3
    2020-04-24 16:20:44.901816+0800 lock[48829:11985929] 进入线程2
    2020-04-24 16:20:44.901860+0800 lock[48829:11985931] 进入线程1
    2020-04-24 16:20:44.902052+0800 lock[48829:11985930] 执行任务3
    2020-04-24 16:20:46.906596+0800 lock[48829:11985930] 退出线程3
    2020-04-24 16:20:46.906618+0800 lock[48829:11985929] 执行任务2
    2020-04-24 16:20:51.908623+0800 lock[48829:11985931] 执行任务1
    2020-04-24 16:20:51.908629+0800 lock[48829:11985929] 退出线程2
    2020-04-24 16:20:52.913340+0800 lock[48829:11985931] 退出线程1
    

    2.4 递归锁NSRecursiveLock

    NSRecursiveLock互斥锁中的递归锁,可被 同一线程多次获取,而不会产生死锁。什么意思呢,一个线程已经获得了锁,开始执行受锁保护的代码(锁还未释放),如果这段代码调用了其他函数,而被调用的函数又要获取这个锁,此时已然可以获得锁并正常执行,而不会死锁。
    基本用法:

    - (void)testLock{
        self.lock = [[NSRecursiveLock alloc] init];
        [NSThread detachNewThreadSelector:@selector(testLock1) toTarget:self withObject:nil];
        [NSThread detachNewThreadSelector:@selector(testLock3) toTarget:self withObject:nil];
    }
    
    - (void)testLock1 {
        [self.lock lock];
        NSLog(@"testLock1");
        [self testLock2];
        [self.lock unlock];
        NSLog(@"testLock1: unlock");
    }
    
    - (void)testLock2 {
        [self.lock lock];
        NSLog(@"testLock2");
        [self.lock unlock];
        NSLog(@"testLock2: unlock");
    }
    
    - (void)testLock3 {
        [self.lock lock];
        NSLog(@"testLock3: %@", [NSThread currentThread]);
        [self.lock unlock];
        NSLog(@"testLock3: unlock");
    }
    

    2.5 对象锁/同步锁 @synchronized

    @synchronized(id)的使用应该是较多的,它底层实现是个递归锁,不会产生死锁,且不需要手动去加锁解锁,使用起来比较方便

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

    信号量的初始值,可以用来控制线程并发访问的最大数量
    信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步
    GCD的信号量是对系统内核信号量的一层封装,要想更深入的了解,可以去研究一下Linux内核的信号量。

    相关文章

      网友评论

          本文标题:iOS 锁的种类和性能

          本文链接:https://www.haomeiwen.com/subject/rlejldtx.html