代码改变世界

iOS开发系列-Lock

2018-04-25 10:47  iCoderHong  阅读(348)  评论(0编辑  收藏  举报

概述

我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生。

iOS中锁之前的性能的图标排行:

开发中常接触的就是NSLock与@synchronized,其它的后续在研究。

NSLock

NSLock是Foundation提供的类,NSLock的API很少也很简单。常用的就几个方法

- (void)lock;
- (void)unlock;
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

lock跟unlock会是成对出现,如果是同一个锁对象lock多处代码。后加锁的代码要想执行必须要等前面加锁的代码先执行完毕并解锁才可执行。
如下示例代码

    NSLock *lock = [NSLock new];
    //Thread 1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 尝试加速ing...");
        [lock lock];
        sleep(5);//睡眠5秒
        NSLog(@"线程1业务处理代码处理");
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    //Thread 2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 尝试加速ing...");
        [lock lock];
        NSLog(@"线程2业务处理代码处理");
        [lock unlock];
        NSLog(@"线程2解锁成功");
    });

控制台输出

线程1跟线程2是两条线程并发执行的,线程1的业务处理逻辑代码先被加锁,当线程2执行到lock实例再次调用lock加锁,其实lock实例已经加过锁了已经无法再加锁此时线程2就一直等待直到之前的锁解锁,5s之后线程1解锁,线程2此时加锁成功就继续执行后续代码。可以看出NSLock是线程堵塞的。

上面的lock方式会阻塞线程,直到之前的lock解锁。NSLock还提供了一个方法

- (BOOL)lockBeforeDate:(NSDate *)limit;

参数传入的是时间,表示会在传入的时间内尝试加锁,若能加锁则执行加锁操作并返回YES,反之返回NO。

    NSLock *lock = [NSLock new];
    //Thread 1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 尝试加速ing...");
        [lock lock];
        sleep(5);//睡眠5秒
        NSLog(@"线程1业务处理代码处理");
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    //Thread 2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 尝试加速ing...");
        BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]]; //从现在开始4s内尝试加锁若能加锁则返回YES,反之返回NO
        if (x) {
            NSLog(@"-------------尝试加锁成功!!!!");
            NSLog(@"线程2业务处理代码处理");
            [lock unlock];
        }else{
            NSLog(@"-------------尝试加锁失败!!!!");
        }
        NSLog(@"线程2解锁成功");
    });
}

线程2执行lockBeforeDate:现在开始的4s内尝试加锁,线程休眠5s,线程2在4s内尝试加锁会失败,返回NO。继续执行后续代码不会一直等待线程1解锁。

dispatch_semaphore 信号量

dispatch_semaphore_t信号量使用主要有如下几个函数

dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

先说下dispatch_semaphore_t信号量使用的原理。

  • dispatch_semaphore_create初始化信号量传入一个初始值value,API说明这个value要大于0
  • dispatch_semaphore_wait就会对信号量-1
  • dispatch_semaphore_signal就会对信号量+1

为了更好的理解,初始化的信号量就好比一个停车位,一开始这个停车位就在dispatch_semaphore_create给确定了。如果有车来了停车位-1,相当与调用了dispatch_semaphore_wait,信号量-1。如果车位已经满了又来了车那么就需要等待。当有其它车开走了后停车位+1,相当于调用了dispatch_semaphore_signal,信号量+1。有了车位,等待的车就可以停车了。当然如果停车位满了后续的车辆可以不用一直等待,可以设置一个时间,如果在此时间内还没有车位就不等待了。

// 初始化信号量 设置信号量的值为1
    dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并等待timeout,时间到后会执行其后的语句
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程1 等待ing");
        dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER ); //signal 值 -1
        sleep(5);
        NSLog(@"线程1");
        dispatch_semaphore_signal(signal); //signal 值 +1
        NSLog(@"线程1 发送信号");
        NSLog(@"--------------------------------------------------------");
        
    });
    
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"线程2 等待ing");
        dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER );
        NSLog(@"线程2");
        dispatch_semaphore_signal(signal);
        NSLog(@"线程2 发送信号");
    });

控制台输出:

这里dispatch_semaphore_wait传入的尝试锁的时间是DISPATCH_TIME_FOREVER就一直等待,自己可以需求传入时间。比如

dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);