美文网首页
iOS 笔记 - 锁

iOS 笔记 - 锁

作者: Luffy_Fit | 来源:发表于2018-10-19 17:48 被阅读0次

    今天简单写一下iOS中相关锁的内容,下图来自不再安全的 OSSpinLock中几种常见的锁加解锁的时间。

    image
    以下为我自己测试的结果,加上了iOS10以后的 os_unfair_lock_lock
    OSSpinLock:                 333.91 ms
    os_unfair_lock_lock:        420.40 ms
    dispatch_semaphore:         374.38 ms
    pthread_mutex:              459.84 ms
    NSCondition:                465.79 ms
    NSLock:                     470.10 ms
    pthread_mutex(recursive):   800.02 ms
    NSRecursiveLock:            712.30 ms
    //多次测试NSRecursiveLock与pthread_mutex的递归锁各有先后,水平有限不知道为啥
    //还请有知道的大神不吝赐教。
    NSConditionLock:           1581.54 ms
    @synchronized:             1980.12 ms
    ---- 加解锁 (10000000)次  测试设备 6s-iOS11.4 ----
    
    
    废弃的OSSpinLock

    OSSpinLock(自旋锁)是属于busy-waiting类型的锁,与互斥锁不同,当SpinLock被其它线程持有,spinLock不会被阻塞,而会一直的请求获取lock,从而消耗大量cpu资源。所以当临界区任务时间较长时,并不适合用SpinLock。但当任务时间较短,其效率贼高。

    另外开头已经提到 OSSpinLock之所以不再使用,是因为当低优先级的线程获得了锁,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU,此时低优先级线程无法与高优先级线程争夺 CPU 时间,导致任务无法完成。这就是优先级反转。

    时间片轮转算法,每个线程会被分配一段时间片(quantum),通常在 10-100 毫秒左右。当线程用完属于自己的时间片以后,就会被操作系统挂起,放入等待队列中,直到下一次被分配时间片。

    os_unfair_lock_t

    os_unfair_lock_t是官方推荐的替代OSSpinLock的方案,优化了优先级反转问题。

     os_unfair_lock_t lock = &(OS_UNFAIR_LOCK_INIT);;
     os_unfair_lock_lock(lock);
     os_unfair_lock_unlock(lock);
    
    dispatch_semaphore

    dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来使用。在没有等待情况出现时,它的性能比 pthread_mutex 还要高。但一旦有等待情况出现时,会线程进入睡眠状态,主动让出时间片,让出时间片会导致操作系统切换到另一个线程,这就是所谓的上下文切换,通常需要 10 微秒左右,而且至少需要两次切换。如果等待时间很短,比如只有几个微秒,忙等就比线程睡眠更高效。
    关于dispatch_semaphore在深入理解GCD中写的也很详细。

    dispatch_semaphore_t lock =  dispatch_semaphore_create(1);
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    dispatch_semaphore_signal(lock);
    
    NSLock & pthread_mutex

    POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。
    pthread_mutex 表示互斥锁。互斥锁的实现原理与信号量非常相似,不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换。
    NSLock底层也是使用pthread_mutex实现,属性为PTHREAD_MUTEX_ERRORCHECK,因为多了方法发送等流程,多次调用后因为方法缓存两者的差距很小。

    pthread_mutex_t lock;
    pthread_mutex_init(&lock, NULL);
    pthread_mutex_lock(&lock);
    pthread_mutex_unlock(&lock);
    
    NSLock *lock = [NSLock new];
    [lock lock];
    [lock unlock];
    
    pthread_mutex(recursive)& NSRecursiveLock

    NSRecursiveLock与NSLock类似,也是使用pthread_mutex实现,只是类型为 PTHREAD_MUTEX_RECURSIVE。
    一般情况下,一个线程只能申请一次锁,也只能在获得锁的情况下才能释放锁,多次申请锁或释放未获得的锁都会导致崩溃。假设在已经获得锁的情况下再次申请锁,线程会因为等待锁的释放而进入睡眠状态,因此就不可能再释放锁,从而导致死锁。
    然而这种情况经常会发生,比如某个函数申请了锁,在临界区内又递归调用了自己,由此也就引出了递归锁:允许同一个线程在未释放其拥有的锁时反复对该锁进行加锁操作。

    pthread_mutex_t lock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&lock, &attr);
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_lock(&lock);
    //do work
    pthread_mutex_unlock(&lock);
    
    NSCondition

    NSCondition 其实是封装了一个互斥锁和条件变量, 它把前者的 lock 方法和后者的 wait/signal 统一在 NSCondition 对象中,暴露给使用者。所以与NSLock基本类似,性能也很接近。

    NSCondition *lock = [NSCondition new];
    [lock lock];
    //do work
    [lock unlock];
    

    NSConditionLock 借助 NSCondition 来实现,它的本质就是一个生产者-消费者模型。“条件被满足”可以理解为生产者提供了新的内容。NSConditionLock 的内部持有一个 NSCondition 对象,以及 _condition_value 属性,在初始化时就会对这个属性进行赋值。
    它的 lockWhenCondition 方法其实就是消费者方法:

    - (void) lockWhenCondition: (NSInteger)value {
        [_condition lock];
        while (value != _condition_value) {
            [_condition wait];
        }
    }
    

    对应的 unlockWhenCondition 方法则是生产者,使用了 broadcast 方法通知了所有的消费者:

    - (void) unlockWithCondition: (NSInteger)value {
        _condition_value = value;
        [_condition broadcast];
        [_condition unlock];
    }
    
    @synchronized

    从上图可以看出@synchronized 性能是最差,语法最为简单
    每个调用 sychronized 的对象,Objective-C runtime 都会为其分配一个递归锁并存储在哈希表中。
    具体实现可以看这关于 @synchronized,这儿比你想知道的还要多

    @synchronized(object) {
            //do work
        }
    

    相关文章

      网友评论

          本文标题:iOS 笔记 - 锁

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