Object-C各种锁的实现

工作中我们常常会遇到多线程的问题,比如滑动界面的同时播放音频、子线程请求网络数据并处理等等。当不同的线程同时处理一部分数据,或者不同的线程处理任务有顺序要求时,就需要用到各种锁来保证安全。

总的来说, 安全锁分为两类: 自旋锁和互斥锁。 它们的详细分类如下图:

Object-C各种锁的实现_第1张图片
安全锁.jpg

自旋锁的实现原理是建立公用的临界区, 类似全局变量, 当一个线程在访问临界区内的资源时, 其它线程就需要等待, 直到临界区内的线程处理完毕离开, 才可以进入。 互斥锁也是建立公用临界区, 但是在访问已经占用的临界区时,访问线程会被睡眠, 直到公共区内的线程处理完才被唤醒。 两者的区别就像同样抢着去已经被占用的公共厕所, 自旋锁的处理方式是在厕所内把门锁上; 互斥锁厕所门口蹲着门卫, 直接把来的人干倒。 从实现方式上可以看出自旋锁的效率相对较高。下面介绍下各种锁的实现方式。

自旋锁

OSSpinLock

 __block OSSpinLock theLock = OS_SPINLOCK_INIT;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    OSSpinLockLock(&theLock);
    NSLog(@"需要线程同步的操作1");
    sleep(3);
    OSSpinLockUnlock(&theLock);
});

查询API文档看到OSSpinLock在iOS10.0已经废除, 换成了os_unfair_lock。 查询相关资料了解到是由于OSSpinLock会由于线程优先级反转, 可能会不安全.

os_unfair_lock

__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    os_unfair_lock_lock(&unfairLock);
    NSLog(@"需要线程同步的操作1");
    sleep(3);
    os_unfair_lock_unlock(&unfairLock);
    
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    os_unfair_lock_lock(&unfairLock);
    NSLog(@"需要线程同步的操作2");
    sleep(1);
    os_unfair_lock_unlock(&unfairLock);
});

打印的结果为:

2017-09-05 10:34:36.895281+0800 TimeTest[810:810984] 需要线程同步的操作2
2017-09-05 10:34:37.900904+0800 TimeTest[810:810977] 需要线程同步的操作1

可以看出优先级较高的线程确实会抢先申请锁资源。 但是在实际使用中, OSSpinLock不安全的情况却不多(暂时没有找到实际应用中不安全的场景)。

互斥锁

dispatch_semaphore_t

dispatch_semaphore_t signal;
signal = dispatch_semaphore_create(2);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    NSLog(@"run task 1");
    sleep(1);
    NSLog(@"end task 1");
    dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    NSLog(@"run task 2");
    sleep(2);
    NSLog(@"end task 2");
    dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    NSLog(@"run task 3");
    sleep(3);
    NSLog(@"end task 3");
    dispatch_semaphore_signal(signal);
});

dispatch_semaphore_create设置线程并发数signal, 上面的代码将并发数设置为2。 dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)将设置的并发数signal减1,接收dsema和timeout两个参数。 当signal的信号为0时,该线程会等待timeout的时间后执行后面的代码。 dispatch_semaphore_signal将并发数signal加1, 一般 和dispatch_semaphore_signal成对出现。 上面的例子中, task1和task2会在signal分配的2个线程中执行, task3会在task1或者task2执行完成后再进入分配的两个线程执行。

pthread_mutex_lock

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);  // 定义锁的属性
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    pthread_mutex_lock(&mutex);
    sleep(2);
    pthread_mutex_unlock(&mutex);
});

pthread_mutex_lock是NSLock, NSRecursiveLock的底层实现。 可以通过pthread_mutexattr_settype来设置创建锁的类型。

NSLock

NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    static void (^RecursiveMethod)(int);
    
    RecursiveMethod = ^(int value){
        [lock lock];
        if (value > 0) {
            NSLog(@"value = %zd", value);
            sleep(2);
            RecursiveMethod(value - 1);
        }
        [lock unlock];
    };
    RecursiveMethod(5);
});

上面的线程中的递归块RecursiveMethod,在调用lock方法后, 会在递归中再次调用lock方法申请锁资源,造成该线程睡眠而死锁。

*** -[NSLock lock]: deadlock ( '(null)')
*** Break on _NSLockError() to debug.

采用NSRecursiveLock可以解决。

NSRecursiveLock

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    static void (^RecursiveMethod)(int);
    
    RecursiveMethod = ^(int value){
        [lock lock];
        if (value > 0) {
            NSLog(@"value = %zd", value);
            sleep(2);
            RecursiveMethod(value - 1);
        }
        [lock unlock];
    };
    RecursiveMethod(5);
});

NSConditionLock

NSConditionLock conditionClock = [[NSConditionLock alloc] initWithCondition:1];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionClock lock];
    NSLog(@"Task1 begin");
    sleep(2);
    NSLog(@"Task1 end");
    [conditionClock unlockWithCondition:10];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [conditionClock lockWhenCondition:10];
    NSLog(@"Task2 begin");
    sleep(2);
    NSLog(@"Task2 end");
    [conditionClock unlockWithCondition:20];
});

conditionClock可以调用lock方法锁定线程, 或者调用lockWhenCondition方法, 在满足规定条件值时锁定线程, 用于不同线程间有数据交互的场景中。

时间对比
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);  // 定义锁的属性
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);

CFAbsoluteTime startTime_pthread = CFAbsoluteTimeGetCurrent();
NSUInteger count = 10000 * 100;
for (int i = 0; i < count; i++) {    
    pthread_mutex_lock(&mutex);
    pthread_mutex_unlock(&mutex);    
}
CFAbsoluteTime endTime_pthread = CFAbsoluteTimeGetCurrent();
NSLog(@"time_pthread: %f", endTime_pthread - startTime_pthread);
CFAbsoluteTime startTime_syn = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < count; i++) {
    @synchronized (self) {
        
    }
}
CFAbsoluteTime endTime_syn = CFAbsoluteTimeGetCurrent();
NSLog(@"time_syn: %f", endTime_syn - startTime_syn);
__block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT;
CFAbsoluteTime startTime_unfairLock = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < count; i++) {
    os_unfair_lock_lock(&unfairLock);
    os_unfair_lock_unlock(&unfairLock);
}
CFAbsoluteTime endTime_unfairLock = CFAbsoluteTimeGetCurrent();
NSLog(@"time_unfairLock: %f", endTime_unfairLock - startTime_unfairLock);

结果:

time_pthread: 0.233672
time_syn: 0.591735
time_unfairLock: 0.101001

将pthread_mutex_lock、@ synchronized、 os_unfair_lock分别加锁、解锁100万次, 可以看到它们占用的时间@ synchronized要大大高于pthread_mutex_lock和os_unfair_lock, 也印证了开始对自旋锁性能高于互斥锁的结论。

实际使用中, 简单场景优先推荐使用自旋锁, 用到互斥锁的地方可以根据具体应用场景来选择。

喜欢和关注都是对我的鼓励和支持~

你可能感兴趣的:(Object-C各种锁的实现)