移动开发技术前沿LanguageiOS 知识库

iOS中的锁的介绍及使用

2017-10-23  本文已影响733人  IAMCJ

在平时的开发中经常使用到多线程,在使用多线程的过程中,难免会遇到资源竞争的问题,那我们怎么来避免出现这种问题那?

线程安全是什么?

当一个线程访问数据的时候,其他的线程不能对其进行访问,直到该线程访问完毕。简单来讲就是在同一时刻,对同一个数据操作的线程只有一个。只有确保了这样,才能使数据不会被其他线程影响。而线程不安全,则是在同一时刻可以有多个线程对该数据进行访问,从而得不到预期的结果。

比如写文件和读文件,当一个线程在写文件的时候,理论上来说,如果这个时候另一个线程来直接读取的话,那么得到的结果可能是你无法预料的。

怎么来保证线程安全?

通常我们使用锁的机制来保证线程安全,即确保同一时刻只有同一个线程来对同一个数据源进行访问。
YY大神 的 不再安全的 OSSpinLock 这边博客中列出了各种锁以及性能比较:

性能对比

这里性能比较的只是加锁立马解锁的时间消耗,并没有计算竞争时候的时间消耗。

锁的介绍及简单使用

1.@synchronized

@synchronized是 iOS 中最常见的锁,用法很简单:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self synchronized];
}

- (void)synchronized {
    NSObject * cjobj = [NSObject new];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        @synchronized(cjobj){
            NSLog(@"线程1开始");
            sleep(3);
            NSLog(@"线程1结束");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        @synchronized(cjobj){
            NSLog(@"线程2");
        }
    });
}

控制台输出:
2017-10-18 11:35:13.459194+0800 Thread-Lock[24855:431100] 线程1开始
2017-10-18 11:35:16.460210+0800 Thread-Lock[24855:431100] 线程1结束
2017-10-18 11:35:16.460434+0800 Thread-Lock[24855:431101] 线程2

从上面的控制台输出时间可以看出来,在线程 1 内容全部输出之后,才输出了线程 2 的内容,“线程1结束”与“线程2”都是在“线程1开始”3 秒后输出的。

@synchronized(cjobj) 指令使用的 cjobj 为该锁的唯一标识,只有当标识相同时,才为满足互斥,如果线程 2 中的 @synchronized(cjobj) 改为 @synchronized(self) ,那么线程 2 就不会被阻塞,@synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

@sychronized(cjobj){} 内部 cjobj 被释放或被设为 nil 不会影响锁的功能,但如果 cjobj 一开始就是 nil,那就会丢失了锁的功能了。

2.NSLock

先看看iOS中NSLock类的.h文件,从代码中可以看出,该类分成了几个子类:NSLock、NSConditionLock、NSRecursiveLock、NSCondition,然后有一个 NSLocking 协议:

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

虽然 NSLock、NSConditionLock、NSRecursiveLock、NSCondition 都遵循的了 NSLocking 协议,但是它们并不相同。

2.1 NSLock

NSLock 实现了最基本的互斥锁,遵循了 NSLocking 协议,通过 lock 和 unlock 来进行锁定和解锁。

源码内容:

@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

用法:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self nslock];
}

- (void)nslock {
    NSLock * cjlock = [NSLock new];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cjlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [cjlock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [cjlock lock];
        NSLog(@"线程2加锁成功");
        [cjlock unlock];
        NSLog(@"线程2解锁成功");
    });
}

控制台输出:
2017-10-19 15:03:58.868708+0800 Thread-Lock[39059:846493] 线程1加锁成功
2017-10-19 15:04:00.872714+0800 Thread-Lock[39059:846493] 线程1解锁成功
2017-10-19 15:04:00.872722+0800 Thread-Lock[39059:846492] 线程2加锁成功
2017-10-19 15:04:00.873000+0800 Thread-Lock[39059:846492] 线程2解锁成功


- (void)nslock {
    NSLock * cjlock = [NSLock new];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cjlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [cjlock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([cjlock tryLock]) {
            NSLog(@"线程3加锁成功");
            [cjlock unlock];
            NSLog(@"线程3解锁成功");
        }else {
            NSLog(@"线程3加锁失败");
        }
    });
}

控制台输出:
2017-10-19 15:05:38.627767+0800 Thread-Lock[39118:849171] 线程1加锁成功
2017-10-19 15:05:38.627767+0800 Thread-Lock[39118:849169] 线程3加锁失败
2017-10-19 15:05:40.629969+0800 Thread-Lock[39118:849171] 线程1解锁成功

- (void)nslock {
    NSLock * cjlock = [NSLock new];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cjlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [cjlock unlock];
        NSLog(@"线程1解锁成功");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(3);
        if ([cjlock tryLock]) {
            NSLog(@"线程4加锁成功");
            [cjlock unlock];
            NSLog(@"线程4解锁成功");
        }else {
            NSLog(@"线程4加锁失败");
        }
    });
}

控制台输出:
2017-10-19 15:07:14.872279+0800 Thread-Lock[39166:851060] 线程1加锁成功
2017-10-19 15:07:16.876108+0800 Thread-Lock[39166:851060] 线程1解锁成功
2017-10-19 15:07:17.876208+0800 Thread-Lock[39166:851052] 线程4加锁成功
2017-10-19 15:07:17.876527+0800 Thread-Lock[39166:851052] 线程4解锁成功

- (void)nslock {
    NSLock * cjlock = [NSLock new];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cjlock lock];
        NSLog(@"线程1加锁成功");
        sleep(2);
        [cjlock unlock];
        NSLog(@"线程1解锁成功");
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([cjlock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程5加锁成功");
            [cjlock unlock];
            NSLog(@"线程5解锁成功");
        }else {
            NSLog(@"线程5加锁失败");
        }
    });
}

控制台输出:
2017-10-19 15:08:39.705131+0800 Thread-Lock[39204:852782] 线程1加锁成功
2017-10-19 15:08:41.708717+0800 Thread-Lock[39204:852782] 线程1解锁成功
2017-10-19 15:08:41.708717+0800 Thread-Lock[39204:852784] 线程5加锁成功
2017-10-19 15:08:41.708935+0800 Thread-Lock[39204:852784] 线程5解锁成功

注意:lock与unlock操作必须在同一线程,否则结果不确定甚至会引起死锁

由以上内容总结:

2.2 NSRecursiveLock

NSRecursiveLock 是递归锁,顾名思义,可以被一个线程多次获得,而不会引起死锁。它记录了成功获得锁的次数,每一次成功的获得锁,必须有一个配套的释放锁和其对应,这样才不会引起死锁。NSRecursiveLock 会记录上锁和解锁的次数,当二者平衡的时候,才会释放锁,其它线程才可以上锁成功。

源码内容:

@interface NSRecursiveLock : 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

用法:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self nsrecursivelock];
}

- (void)nsrecursivelock{
    NSRecursiveLock * cjlock = [[NSRecursiveLock alloc] init];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        static void (^RecursiveBlock)(int);
        RecursiveBlock = ^(int value) {
            [cjlock lock];
            NSLog(@"%d加锁成功",value);
            if (value > 0) {
                NSLog(@"value:%d", value);
                RecursiveBlock(value - 1);
            }
            [cjlock unlock];
            NSLog(@"%d解锁成功",value);
        };
        RecursiveBlock(3);
    });
}

控制台输出:
2017-10-19 16:15:40.584213+0800 Thread-Lock[39579:894111] 3加锁成功
2017-10-19 16:15:40.584387+0800 Thread-Lock[39579:894111] value:3
2017-10-19 16:15:40.584552+0800 Thread-Lock[39579:894111] 2加锁成功
2017-10-19 16:15:40.584635+0800 Thread-Lock[39579:894111] value:2
2017-10-19 16:15:40.584810+0800 Thread-Lock[39579:894111] 1加锁成功
2017-10-19 16:15:40.585267+0800 Thread-Lock[39579:894111] value:1
2017-10-19 16:15:40.585714+0800 Thread-Lock[39579:894111] 0加锁成功
2017-10-19 16:15:40.585906+0800 Thread-Lock[39579:894111] 0解锁成功
2017-10-19 16:15:40.586138+0800 Thread-Lock[39579:894111] 1解锁成功
2017-10-19 16:15:40.586217+0800 Thread-Lock[39579:894111] 2解锁成功
2017-10-19 16:15:40.586314+0800 Thread-Lock[39579:894111] 3解锁成功

由以上内容总结:

2.3 NSConditionLock

NSConditionLock 对象所定义的互斥锁可以在使得在某个条件下进行锁定和解锁,它和 NSLock 类似,都遵循 NSLocking 协议,方法都类似,只是多了一个 condition 属性,以及每个操作都多了一个关于 condition 属性的方法,例如 tryLock、tryLockWhenCondition:,所以 NSConditionLock 可以称为条件锁。

源码内容:

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

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

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

@end

用法:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self nsconditionlock];
}

- (void)nsconditionlock {
    NSConditionLock * cjlock = [[NSConditionLock alloc] initWithCondition:0];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [cjlock lock];
        NSLog(@"线程1加锁成功");
        sleep(1);
        [cjlock unlock];
        NSLog(@"线程1解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        [cjlock lockWhenCondition:1];
        NSLog(@"线程2加锁成功");
        [cjlock unlock];
        NSLog(@"线程2解锁成功");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(2);
        if ([cjlock tryLockWhenCondition:0]) {
            NSLog(@"线程3加锁成功");
            sleep(2);
            [cjlock unlockWithCondition:2];
            NSLog(@"线程3解锁成功");
        } else {
            NSLog(@"线程3尝试加锁失败");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if ([cjlock lockWhenCondition:2 beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程4加锁成功");
            [cjlock unlockWithCondition:1];
            NSLog(@"线程4解锁成功");
        } else {
            NSLog(@"线程4尝试加锁失败");
        }
    });
}

控制台输出:
2017-10-19 15:09:44.010992+0800 Thread-Lock[39230:853946] 线程1加锁成功
2017-10-19 15:09:45.012045+0800 Thread-Lock[39230:853946] 线程1解锁成功
2017-10-19 15:09:46.012692+0800 Thread-Lock[39230:853947] 线程3加锁成功
2017-10-19 15:09:48.016536+0800 Thread-Lock[39230:853947] 线程3解锁成功
2017-10-19 15:09:48.016564+0800 Thread-Lock[39230:853944] 线程4加锁成功
2017-10-19 15:09:48.017039+0800 Thread-Lock[39230:853944] 线程4解锁成功
2017-10-19 15:09:48.017040+0800 Thread-Lock[39230:853945] 线程2加锁成功
2017-10-19 15:09:48.017215+0800 Thread-Lock[39230:853945] 线程2解锁成功

由以上内容总结:

2.4、NSCondition

NSCondition 是一种特殊类型的锁,通过它可以实现不同线程的调度。一个线程被某一个条件所阻塞,直到另一个线程满足该条件从而发送信号给该线程使得该线程可以正确的执行。比如说,你可以开启一个线程下载图片,一个线程处理图片。这样的话,需要处理图片的线程由于没有图片会阻塞,当下载线程下载完成之后,则满足了需要处理图片的线程的需求,这样可以给定一个信号,让处理图片的线程恢复运行。

源码内容:

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

- (void)wait; //挂起线程
- (BOOL)waitUntilDate:(NSDate *)limit; //什么时候挂起线程
- (void)signal; // 唤醒一条挂起线程
- (void)broadcast; //唤醒所有挂起线程

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

@end

用法:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self nscondition];
}

- (void)nscondition {
    NSCondition * cjcondition = [NSCondition new];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [cjcondition lock];
        NSLog(@"线程1线程加锁");
        [cjcondition wait];
        NSLog(@"线程1线程唤醒");
        [cjcondition unlock];
        NSLog(@"线程1线程解锁");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [cjcondition lock];
        NSLog(@"线程2线程加锁");
        if ([cjcondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]) {
            NSLog(@"线程2线程唤醒");
            [cjcondition unlock];
            NSLog(@"线程2线程解锁");
        }
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [cjcondition signal];
    });
}

控制台输出:
2017-10-19 17:15:48.410316+0800 Thread-Lock[40011:943638] 线程1线程加锁
2017-10-19 17:15:48.410757+0800 Thread-Lock[40011:943640] 线程2线程加锁
2017-10-19 17:15:50.414288+0800 Thread-Lock[40011:943638] 线程1线程唤醒
2017-10-19 17:15:50.414454+0800 Thread-Lock[40011:943638] 线程1线程解锁

//如果 [cjcondition signal]; 改成 [cjcondition broadcast];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(2);
        [cjcondition broadcast];
    });

控制台输出:
2017-10-19 17:18:08.054109+0800 Thread-Lock[40056:946099] 线程1线程加锁
2017-10-19 17:18:08.054304+0800 Thread-Lock[40056:946096] 线程2线程加锁
2017-10-19 17:18:10.056071+0800 Thread-Lock[40056:946099] 线程1线程唤醒
2017-10-19 17:18:10.056231+0800 Thread-Lock[40056:946099] 线程1线程解锁
2017-10-19 17:18:10.056244+0800 Thread-Lock[40056:946096] 线程2线程唤醒
2017-10-19 17:18:10.056445+0800 Thread-Lock[40056:946096] 线程2线程解锁

由以上内容总结:

3.dispatch_semaphore

dispatch_semaphore 使用信号量机制实现锁,等待信号和发送信号。

常用相关API:

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

用法:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self dispatch_semaphore];
}

- (void)dispatch_semaphore {
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, overTime);
        NSLog(@"线程1开始");
        sleep(5);
        NSLog(@"线程1结束");
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        dispatch_semaphore_wait(semaphore, overTime);
        NSLog(@"线程2开始");
        dispatch_semaphore_signal(semaphore);
    });
}

控制台输出:
2017-10-19 18:30:37.672490+0800 Thread-Lock[40569:993613] 线程1开始
2017-10-19 18:30:42.673845+0800 Thread-Lock[40569:993613] 线程1结束
2017-10-19 18:30:42.674165+0800 Thread-Lock[40569:993612] 线程2开始

//如果 overTime 改成 3 秒
控制台输出:
2017-10-19 18:32:32.078186+0800 Thread-Lock[40634:995921] 线程1开始
2017-10-19 18:32:35.082943+0800 Thread-Lock[40634:995920] 线程2开始
2017-10-19 18:32:37.083115+0800 Thread-Lock[40634:995921] 线程1结束

由以上内容总结:

4.pthread_mutex 与 pthread_mutex(recursive)

pthread 表示 POSIX thread,定义了一组跨平台的线程相关的 API,POSIX 互斥锁是一种超级易用的互斥锁,使用的时候:

常用相关API:

pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable);
pthread_mutex_lock(pthread_mutex_t * _Nonnull);
pthread_mutex_trylock(pthread_mutex_t * _Nonnull);
pthread_mutex_unlock(pthread_mutex_t * _Nonnull);
pthread_mutex_destroy(pthread_mutex_t * _Nonnull);

用法:

//pthread_mutex

- (void)viewDidLoad {
    [super viewDidLoad];

    [self pthread_mutex];
}

- (void)pthread_mutex {
    __block pthread_mutex_t cjlock;
    pthread_mutex_init(&cjlock, NULL);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&cjlock);
        NSLog(@"线程1开始");
        sleep(3);
        NSLog(@"线程1结束");
        pthread_mutex_unlock(&cjlock);
        
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        pthread_mutex_lock(&cjlock);
        NSLog(@"线程2");
        pthread_mutex_unlock(&cjlock);
    });
}

控制台输出:
2017-10-23 14:50:29.842180+0800 Thread-Lock[74478:1647362] 线程1开始
2017-10-23 14:50:32.846786+0800 Thread-Lock[74478:1647362] 线程1结束
2017-10-23 14:50:32.847001+0800 Thread-Lock[74478:1647359] 线程2


//pthread_mutex(recursive)

- (void)viewDidLoad {
    [super viewDidLoad];

    [self pthread_mutex_recursive];
}

- (void)pthread_mutex_recursive {
    __block pthread_mutex_t cjlock;
    
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&cjlock, &attr);
    pthread_mutexattr_destroy(&attr);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        static void (^RecursiveBlock)(int);
        
        RecursiveBlock = ^(int value) {
            pthread_mutex_lock(&cjlock);
            NSLog(@"%d加锁成功",value);
            if (value > 0) {
                NSLog(@"value = %d", value);
                sleep(1);
                RecursiveBlock(value - 1);
            }
            NSLog(@"%d解锁成功",value);
            pthread_mutex_unlock(&cjlock);
        };
        RecursiveBlock(3);
    });
}

//控制台输出:
2017-10-23 15:31:51.599693+0800 Thread-Lock[74723:1668089] 3加锁成功
2017-10-23 15:31:51.599912+0800 Thread-Lock[74723:1668089] value = 3
2017-10-23 15:31:52.602002+0800 Thread-Lock[74723:1668089] 2加锁成功
2017-10-23 15:31:52.602317+0800 Thread-Lock[74723:1668089] value = 2
2017-10-23 15:31:53.604669+0800 Thread-Lock[74723:1668089] 1加锁成功
2017-10-23 15:31:53.604957+0800 Thread-Lock[74723:1668089] value = 1
2017-10-23 15:31:54.607778+0800 Thread-Lock[74723:1668089] 0加锁成功
2017-10-23 15:31:54.608109+0800 Thread-Lock[74723:1668089] 0解锁成功
2017-10-23 15:31:54.608391+0800 Thread-Lock[74723:1668089] 1解锁成功
2017-10-23 15:31:54.608622+0800 Thread-Lock[74723:1668089] 2解锁成功
2017-10-23 15:31:54.608945+0800 Thread-Lock[74723:1668089] 3解锁成功

由以上内容总结:

5. OSSpinLock

OSSpinLock 是一种自旋锁,和互斥锁类似,都是为了保证线程安全的锁。但二者的区别是不一样的,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。所以,此锁比较适用于锁的持有者保存时间较短的情况下。

常用相关API:

typedef int32_t OSSpinLock;

// 加锁
void    OSSpinLockLock( volatile OSSpinLock *__lock );
// 尝试加锁
bool    OSSpinLockTry( volatile OSSpinLock *__lock );
// 解锁
void    OSSpinLockUnlock( volatile OSSpinLock *__lock );

用法:

#import <libkern/OSAtomic.h>

- (void)viewDidLoad {
    [super viewDidLoad];

    [self osspinlock];
}

- (void)osspinlock {
    __block OSSpinLock theLock = OS_SPINLOCK_INIT;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"线程1开始");
        sleep(3);
        NSLog(@"线程1结束");
        OSSpinLockUnlock(&theLock);
        
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        sleep(1);
        NSLog(@"线程2");
        OSSpinLockUnlock(&theLock);
        
    });
}

//控制台输出:
2017-10-23 16:02:48.865501+0800 Thread-Lock[75025:1684487] 线程1开始
2017-10-23 16:02:51.868736+0800 Thread-Lock[75025:1684487] 线程1结束
2017-10-23 16:02:52.922911+0800 Thread-Lock[75025:1684486] 线程2

YY大神 @ibireme 的文章也有说这个自旋锁存在优先级反转问题,具体文章可以戳 不再安全的 OSSpinLock,而 OSSpinLock 在iOS 10.0中被 <os/lock.h> 中的 os_unfair_lock 取代。

6.os_unfair_lock

自旋锁已经不再安全,然后苹果又整出来个 os_unfair_lock,这个锁解决了优先级反转问题。

常用相关API:

// 初始化
os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
// 加锁
os_unfair_lock_lock(unfairLock);
// 尝试加锁
BOOL b = os_unfair_lock_trylock(unfairLock);
// 解锁
os_unfair_lock_unlock(unfairLock);

os_unfair_lock 用法和 OSSpinLock 基本一直,就不一一列出了。

总结

应当针对不同的操作使用不同的锁,而不能一概而论哪种锁的加锁解锁速度快。

如果想了解每个锁的实现原理可以看一下这个,非常赞:
深入理解 iOS 开发中的锁

参考:
iOS中保证线程安全的几种方式与性能对比
不再安全的 OSSpinLock
iOS 中的各种锁
关于@synchronized,这儿比你想知道的还要多

上一篇 下一篇

猜你喜欢

热点阅读