iOS中的线程锁(关于NSLock)

1,069 阅读2分钟

「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

NSLock(互斥锁)

NSLock 是 Cocoa提供的最基本的锁对象,它是这样被定义的:

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}

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

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

关于 lock 和 unlock

- (void)viewDidLoad {
    [super viewDidLoad];
   
    NSLock *lock = [[NSLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [lock lock];
        NSLog(@"线程1");
        sleep(2);
        
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保证让线程2的代码后执行
        [lock lock];
        NSLog(@"线程2");
        [lock unlock];
    });
}

log:

Snip20211115_9.png

线程1 中的 lock 锁上了,所以线程2 中的 lock 加锁失败,阻塞线程2(线程2的log没有打印),但2s后线程1 中的 lock 解锁,线程2 就立即加锁成功,执行线程2中的后续代码

互斥锁会使得线程阻塞,阻塞的过程又分两个阶段,第一阶段是会先空转,可以理解成跑一个 while 循环,不断地去申请加锁,在空转一定时间之后,线程会进入 waiting 状态,此时线程就不占用CPU资源了,等锁可用的时候,这个线程会立即被唤醒


- (BOOL)tryLock

使用 tryLock 会试图获取一个锁,如果锁不可用的时候,它不会阻塞线程,会直接返回 NO

- (void)viewDidLoad {
    [super viewDidLoad];
   
   NSLock *lock = [[NSLock alloc] init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [lock lock];
        NSLog(@"线程1");
        sleep(2);
        
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保证让线程2的代码后执行
        
        //尝试加锁
        if ([lock tryLock]) {
            [lock lock];
            NSLog(@"线程2");
            [lock unlock];
        }else{
            NSLog(@"线程2尝试加锁失败");
        }
    });
}

log:

Snip20211115_10.png

当线程1 中的 lock 锁上了,所以线程2 中的 tryLock 会尝试加锁,加锁失败会,返回 NO,然后都会执行后续代码


- (BOOL)lockBeforeDate:(NSDate *)limit

使用 lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,会阻塞线程,如果在指定时间之前都不能加锁,则返回 NO,指定时间之前能加锁,则返回 YES

- (void)viewDidLoad {
    [super viewDidLoad];
   
    NSLock *lock = [[NSLock alloc] init];
    
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [lock lock];
        NSLog(@"线程1");
        sleep(5);
        
        [lock unlock];
        NSLog(@"线程1解锁成功");
    });

    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);//以保证让线程2的代码后执行
        
        //尝试在未来的2s内获取锁,并阻塞该线程,如果2s内获取不到恢复线程, 返回NO,不会阻塞该线程
        if ([lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:2]]) {
            [lock lock];
            NSLog(@"线程2");
            [lock unlock];
        }else{
            NSLog(@"线程2尝试加锁失败");
        }
    });
}

log:

Snip20211115_11.png


tryLocklock 的区别

tryLocklock 方法都会请求加锁,唯一不同的是 tryLock 在没有获得锁的时候,不会卡线程,可以继续做一些任务和处理