浅谈iOS-八大锁的使用

当多个线程同时操作同一资源数据时,为了防止ABCDEFG同时修改保存该内容,就得加个锁,使多个行程按照一定的次序去操作该资源

一、NSLock
加锁lock
解锁unlock

失败.png
    NSLock *lock = [[NSLock alloc]init];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [lock lock];
        NSLog(@"线程一");
        sleep(10);
        [lock unlock];
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([lock tryLock]) {
            NSLog(@"线程二");
            sleep(1);
            [lock unlock];
        }else{
            NSLog(@"测试加锁失败");
        }
    });

二、NSConditionLock条件锁
跟NSLock类似,多了个条件锁Condition


条件锁代码.png
条件锁结果.png
        //主线程中
        NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
        
        //线程1
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [lock lockWhenCondition:1];
            NSLog(@"线程1");
            sleep(2);
            [lock unlock];
        });
        
        //线程2
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(5);//以保证让线程2的代码后执行
            if ([lock tryLockWhenCondition:0]) {
                NSLog(@"线程2");
                [lock unlockWithCondition:2];
                NSLog(@"线程2解锁成功");
            } else {
                NSLog(@"线程2尝试加锁失败");
            }
        });
        
        //线程3
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(2);//以保证让线程2的代码后执行
            if ([lock tryLockWhenCondition:2]) {
                NSLog(@"线程3");
                [lock unlock];
                NSLog(@"线程3解锁成功");
            } else {
                NSLog(@"线程3尝试加锁失败");
            }
        });
        
        //线程4
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(3);//以保证让线程2的代码后执行
            if ([lock tryLockWhenCondition:2]) {
                NSLog(@"线程4");
                [lock unlockWithCondition:1];
                NSLog(@"线程4解锁成功");
            } else {
                NSLog(@"线程4尝试加锁失败");
            }
        });

三、递归锁NSRecursiveLock
实际是一个同步锁


企业微信截图_787c61a6-d7b2-483f-9e53-c5bdf304065f.png
#import 
        NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            static void (^RecursiveBlock)(int);
            RecursiveBlock = ^(int value) {
                [lock lock];
                if (value > 0) {
                    NSLog(@"value:%d", value);
                    sleep(3);
                    RecursiveBlock(value - 1);
                }
                [lock unlock];
            };
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"开始睡觉");
                sleep(2);
                NSLog(@"睡了2秒");
                sleep(4);
                NSLog(@"睡了4秒");
            });
            
            NSLog(@"递归一开始");
            RecursiveBlock(5);
            NSLog(@"递归一完成");
            sleep(1);
            RecursiveBlock(3);
        });

四、线程、锁判断NSCondition


企业微信截图_787c61a6-d7b2-483f-9e53-c5bdf304065f.png
    __block NSMutableArray *products = [NSMutableArray array];
    NSCondition *condition = [NSCondition new];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程一开始");
        [condition lock];
        while (products.count<1) {
             NSLog(@"线程一等待");
            //线程等待
            [condition wait];
        }
        NSLog(@"线程一执行");
        [products removeObjectAtIndex:0];
        [condition unlock];
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        NSLog(@"线程二");
        [condition lock];
        [products addObject:@1];
        //唤醒等待的线程
        [condition signal];
        [condition unlock];
    });

五、## @synchronized

    @synchronized (self) {
//some code
    }
    //等价于
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLock *lock = [NSLock new];
        [lock lock];
//some code
        [lock unlock];
    });
 //等价于
    NSString *test = @"test";
    id synchronizeTarget = (id)test;
    @try {
        objc_sync_enter(synchronizeTarget);
        test = nil;
    } @finally {
        objc_sync_exit(synchronizeTarget);
    }

六、信号量dispatch_semaphore_t


企业微信截图_787c61a6-d7b2-483f-9e53-c5bdf304065f.png
dispatch_semaphore_t signal = dispatch_semaphore_create(0);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 9 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);
        NSLog(@"signal");
        dispatch_semaphore_signal(signal);
    });
    NSLog(@"wait");
    dispatch_semaphore_wait(signal, overTime);
    NSLog(@"end");

七、互坼锁mutex

#import 
#import 
static pthread_mutex_t theLock;
- (void)example5 {
/*
PTHREAD_MUTEX_NORMAL 缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。

PTHREAD_MUTEX_ERRORCHECK 检错锁,如果同一个线程请求同一个锁,则返回 EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。

PTHREAD_MUTEX_RECURSIVE 递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。

PTHREAD_MUTEX_DEFAULT 适应锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。
*/
    pthread_mutex_init(&theLock, NULL);
    
//设置类型
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&theLock, &attr);
    pthread_mutexattr_destroy(&attr);

    pthread_t thread;
    pthread_create(&thread, NULL, threadMethord1, NULL);
    
    pthread_t thread2;
    pthread_create(&thread2, NULL, threadMethord2, NULL);
}
void *threadMethord1() {
    pthread_mutex_lock(&theLock);
    printf("线程1\n");
    sleep(2);
    pthread_mutex_unlock(&theLock);
    printf("线程1解锁成功\n");
    return 0;
}
void *threadMethord2() {
    sleep(1);
    pthread_mutex_lock(&theLock);
    printf("线程2\n");
    pthread_mutex_unlock(&theLock);
    printf("线程2解锁成功\n");
    return 0;
}

八、自旋锁OSSpinLock (因为线程安全问题,iOS 10.0以后废弃了,取而代之的是互斥锁os_unfair_lock_lock,类似于pthread_mutex)
自旋锁实际就是轮询监测任务
持有自旋锁的进程也不允许睡眠,不然会造成死锁——因为睡眠可能造成持有锁的进程被重新调度,而再次申请自己已持有的锁
a.自旋锁的反转/资源抢占
优先级:A》B〉C
A等待C调资源R
B本应生成资源R,但是没有生成资源R
导致A一直等待C,C一直等待资源R(而B又不生成资源R)


未标题-1.jpg
企业微信截图_92c2525a-7feb-4bea-8ea6-9e14c793f730.png
#import 
    __block OSSpinLock oslock = OS_SPINLOCK_INIT;
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 准备上锁");
        OSSpinLockLock(&oslock);
        sleep(4);
        NSLog(@"线程1");
        OSSpinLockUnlock(&oslock);
        NSLog(@"线程1 解锁成功");
        NSLog(@"--------------------------------------------------------");
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 准备上锁");
        OSSpinLockLock(&oslock);
        NSLog(@"线程2");
        OSSpinLockUnlock(&oslock);
        NSLog(@"线程2 解锁成功");
    });

性能

在 ibireme 的不再安全的 OSSpinLock 一文中,有贴出这些锁的性能对比,如下图:

1400498-2bfca138992bb7d8.png

当然只是加锁立马解锁的时间消耗,并没有计算竞争时候的时间消耗。可以看出 OSSpinLock 性能最高,但它已经不再安全,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,由于它会处于轮询的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。

图中的 pthread_mutex(recursive) 指的是 pthread_mutex 设置为递归锁的情况。

从图中可以知道 @synchronized 的效率最低,不过它的确用起来最方便,所以如果没什么性能瓶颈的话,使用它也不错。

mutex和spin lock的区别
mutex和spin lock的区别和应用(sleep-waiting和busy-waiting的区别)
既mutex闲时处于休眠状态,使得cpu空缺出来,去做别的任务
而spin lock处于轮询监测等待任务,这个过程中cpu始终处于忙状态,不能做别的任务

例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0 和Core1上。 用spin-lock,coer0上的线程就会始终占用CPU。
另外一个值得注意的细节是spin lock耗费了更多的user time。这就是因为两个线程分别运行在两个核上,大部分时间只有一个线程能拿到锁,所以另一个线程就一直在它运行的core上进行忙等待,CPU占用率一直是100%;而mutex则不同,当对锁的请求失败后上下文切换就会发生,这样就能空出一个核来进行别的运算任务了。(其实这种上下文切换对已经拿着锁的那个线程性能也是有影响的,因为当该线程释放该锁时它需要通知操作系统去唤醒那些被阻塞的线程,这也是额外的开销)

总结:

虽然这些锁看起来很复杂,但最终都是加锁,等待,解锁。一下子懂了八锁,有点小激动。

参考文章

iOS中保证线程安全的几种方式与性能对比
不再安全的 OSSpinLock
关于 @synchronized,这儿比你想知道的还要多
iOS 常见知识点(三):Lock

demo地址

你可能感兴趣的:(浅谈iOS-八大锁的使用)