美文网首页
iOS中的锁

iOS中的锁

作者: lxl125z | 来源:发表于2017-12-14 17:20 被阅读0次

    可以看这篇文章:iOS 常见知识点(三):Lock

    什么是锁

    锁是一种同步机制,用于在存在多线程的环境中实施对资源的访问限制。

    iOS中锁的实现

    使用NSLock类

    NSLock 遵循 NSLocking 协议,lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。

    - (void)nslockDemo {
        NSLock *myLock = [[NSLock alloc] init];
        _testLock = [[TestLock alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [myLock lock];
            [_testLock method1];
            sleep(5);
            [myLock unlock];
            if ([myLock tryLock]) {
                NSLog(@"可以获得锁");
            }else {
                NSLog(@"不可以获得所");
            }
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            if ([myLock tryLock]) {
                NSLog(@"---可以获得锁");
            }else {
                NSLog(@"----不可以获得所");
            }
            [myLock lock];
            [_testLock method2];
            [myLock unlock];
        });
    }
    
    

    NSConditionLock

    NSConditionLock 和 NSLock 类似,都遵循 NSLocking 协议,方法都类似,只是多了一个 condition 属性,以及每个操作都多了一个关于 condition 属性的方法,例如 tryLock,tryLockWhenCondition:,NSConditionLock 可以称为条件锁,只有 condition 参数与初始化时候的 condition 相等,lock 才能正确进行加锁操作。而 unlockWithCondition: 并不是当 Condition 符合条件时才解锁,而是解锁之后,修改 Condition 的值,这个结论可以从下面的例子中得出。

    NSRecursiveLock

    NSRecursiveLock 是递归锁,他和 NSLock 的区别在于,NSRecursiveLock 可以在同一个线程中重复加锁(反正单线程内任务是按顺序执行的,不会出现资源竞争问题),NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。

    PS:解决了NSLOCK多次上锁造成的死锁问题

    NSCondition

    NSCondition 的对象实际上作为一个锁和一个线程检查器,锁上之后其它线程也能上锁,而之后可以根据条件决定是否继续运行线程,即线程是否要进入 waiting 状态,经测试,NSCondition 并不会像上文的那些锁一样,先轮询,而是直接进入 waiting 状态,当其它线程中的该锁执行 signal 或者 broadcast 方法时,线程被唤醒,继续运行之后的方法。

    
    @interface NSCondition : NSObject <NSLocking> {
    @private
        void *_priv;
    }
    
    - (void)wait;
    - (BOOL)waitUntilDate:(NSDate *)limit;
    - (void)signal;
    - (void)broadcast;
    
    @property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
    
    @end
    
    

    @synchorize

    对于@synchorize指令中使用的testLock为该锁标示,只有标示相同的时候才满足锁的效果

    - (void)synchronizeDemo {
        _testLock = [[TestLock alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            @synchronized (_testLock) {
                [_testLock method1];
                sleep(5);
            }
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            @synchronized (_testLock) {
                 
                [_testLock method2];
            }
        });
    }
    
    

    gcd(dispatch_semaphore)

    dispatch_semaphore_t signal = dispatch_semaphore_create(1);
        dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            dispatch_semaphore_wait(signal, overTime);
            sleep(2);
            NSLog(@"线程1");
            dispatch_semaphore_signal(signal);
        });
        
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            dispatch_semaphore_wait(signal, overTime);
            NSLog(@"线程2");
            dispatch_semaphore_signal(signal);
        });
    
    

    dispatch_semaphore 和 NSCondition 类似,都是一种基于信号的同步方式,但 NSCondition 信号只能发送,不能保存(如果没有线程在等待,则发送的信号会失效)。而 dispatch_semaphore 能保存发送的信号。dispatch_semaphore 的核心是 dispatch_semaphore_t 类型的信号量。

    dispatch_semaphore_create(1) 方法可以创建一个 dispatch_semaphore_t 类型的信号量,设定信号量的初始值为 1。注意,这里的传入的参数必须大于或等于 0,否则 dispatch_semaphore_create 会返回 NULL。

    dispatch_semaphore_wait(signal, overTime); 方法会判断 signal 的信号值是否大于 0。大于 0 不会阻塞线程,消耗掉一个信号,执行后续任务。如果信号值为 0,该线程会和 NSCondition 一样直接进入 waiting 状态,等待其他线程发送信号唤醒线程去执行后续任务,或者当 overTime 时限到了,也会执行后续任务。

    dispatch_semaphore_signal(signal); 发送信号,如果没有等待的线程接受信号,则使 signal 信号值加一(做到对信号的保存)。

    从上面的实例代码可以看到,一个 dispatch_semaphore_wait(signal, overTime); 方法会去对应一个 dispatch_semaphore_signal(signal); 看起来像 NSLock 的 lock 和 unlock,其实可以这样理解,区别只在于有信号量这个参数,lock unlock 只能同一时间,一个线程访问被保护的临界区,而如果 dispatch_semaphore 的信号量初始值为 x ,则可以有 x 个线程同时访问被保护的临界区。

    OSSpinLock

    OSSpinLock 是一种自旋锁,也只有加锁,解锁,尝试加锁三个方法。和 NSLock 不同的是 NSLock 请求加锁失败的话,会先轮询,但一秒过后便会使线程进入 waiting 状态,等待唤醒。而 OSSpinLock 会一直轮询,等待时会消耗大量 CPU 资源,不适用于较长时间的任务。

    OSSpinLock出现优先级反转的问题:

    具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。

    pthread_mutex

    pthread_mutex_t定义在pthread.h,所以记得#include。

    - (void)pthreadDemo {
        _testLock = [[TestLock alloc] init];
         
        __block pthread_mutex_t mutex;
        pthread_mutex_init(&mutex, NULL);
         
        //线程1
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            pthread_mutex_lock(&mutex);
            [_testLock method1];
            sleep(5);
            pthread_mutex_unlock(&mutex);
        });
         
        //线程2
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(1);
            pthread_mutex_lock(&mutex);
            [_testLock method2];
            pthread_mutex_unlock(&mutex);
        });
    }
    

    性能对比

    耗用时间: synthorize > NSLock > pthread > gcd

    synthorize内部会添加异常处理,所以耗时。
    pthread_mutex底层API,处理能力不错。
    gcd系统封装的C代码效果比pthread好。

    总的来说:
    OSSpinLock和dispatch_semaphore的效率远远高于其他。
    @synchronized和NSConditionLock效率较差。
    鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。
    如果不考虑性能,只是图个方便的话,那就使用@synchronized。

    相关文章

      网友评论

          本文标题:iOS中的锁

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