iOS - 系统中的锁

多线程同时访问同一块资源会造成资源抢夺,容易引发数据错乱和数据安全问题,此时我们需要保证资源同时只有一个线程访问,加锁就是为了解决这个问题。

常用的加锁方式:(性能由差到好)

  • OSSpinLock 自旋锁,存在优先级反转问题,破坏了 spinLock,后来Apple推出 os_unfair_lock_t 解决优先级翻转问题
  • dispatch_semaphore 信号量
  • pthread_mutex 互斥锁 (C语言)
  • NSLock 对象锁
  • NSCondition 条件锁
  • NSRecursiveLock 递归锁
  • NSConditionLock 条件锁
  • @synchronized 性能最差

各种锁的性能比较

自旋锁

'OSSpinLock'、'os_unfair_lock'

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

spinLock = OS_SPINLOCK_INIT;
OSSpinLockLock(&spinLock);
sleep(4);
OSSpinLockUnlock(&spinLock);

'OSSpinLock' 不再安全了? 因为存在优先级翻转的问题。
在新版本iOS系统中,系统维护了5个不同的线程优先级(Qos): background, utility, default, user-initiated, user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比他更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级翻转的问题,从而破坏 spin lock.

实例:如果一个低优先级的线程获得锁并访问共享资源,此时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙时状态从而占用大量CPU,此时低优先级线程无法与高优先级线程抢夺CPU时间,从而导致任务迟迟无法完成,无法释放lock。

为解决此问题,在iOS10,给出了新API:os_unfair_lock

os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
sleep(4);
os_unfair_lock_unlock(unfairLock);

信号量

dispatch_semaphore

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量

dispatch_semaphore 可以通过控制并发数实现锁机制。通过控制信号量的值为 0 或 1 来实现锁。dispatch_semaphore_wait 会使信号量-1,dispatch_semaphore_signal 会使信号量+1,信号量为0时线程等待,为1时方可继续执行。

#import "Dispatch_Semaphore.h"
@interface Dispatch_Semaphore ()
{
    dispatch_semaphore_t _semaphore;
}
@property (nonatomic, strong) NSMutableArray *array;
@property (assign) NSUInteger index;
@end
@implementation Dispatch_Semaphore
- (instancetype)init {
    self = [super init];
    if (self) {
        /// 通道默认为1, 首次执行
        _semaphore = dispatch_semaphore_create(1);
    }
    return self;
}
- (void)run {
    for (NSUInteger i = 0; i < self.array.count; i++) {
        if (i % 2 == 0) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self log];
            });
        } else {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self log];
            });
        }
    }
}
- (void)log {
    /// 通道 -1 阻塞其他线程进入
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"%@",self.array[self.index++]);
    /// 通道+1 其他线程可进入
    dispatch_semaphore_signal(_semaphore);
}
- (NSMutableArray *)array {
    if(!_array){
        NSMutableArray *arrayM = [NSMutableArray array];
        for (NSUInteger i = 0; i < 1000; i++) {
            [arrayM addObject:@(i)];
        }
        _array = arrayM;
    }
    return _array;
}
@end

互斥锁

pthread_mutex

#import "Phread_mutex.h"
#import <pthread.h>
@interface Phread_mutex ()
{
    pthread_mutex_t _mutex_t;
}
@property (nonatomic, strong) NSMutableArray *array;
@property (assign) NSUInteger index;
@end

@implementation Phread_mutex
- (instancetype)init {
    self = [super init];
    if (self) {
        /// 锁初始化
        pthread_mutex_t mutex_t = PTHREAD_MUTEX_INITIALIZER;
        _mutex_t = mutex_t;
    }
    return self;
}
- (void)run {
    for (NSUInteger i = 0; i < self.array.count; i++) {
        if (i % 2 == 0) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self log];
            });
        } else {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self log];
            });
        }
    }
}
- (void)log {
    // 加互斥锁
    pthread_mutex_lock(&(_mutex_t));
    NSLog(@"%@",self.array[self.index++]);
    // 解互斥锁
    pthread_mutex_unlock(&(_mutex_t));
}
- (NSMutableArray *)array {
    if(!_array){
        NSMutableArray *arrayM = [NSMutableArray array];
        for (NSUInteger i = 0; i < 1000; i++) {
            [arrayM addObject:@(i)];
        }
        _array = arrayM;
    }
    return _array;
}
@end

NSLock

NSLockNSRecursiveLock, NSConditionLock 都是对 pthread_mutex 的封装。

NSLock 遵循 NSLocking 协议. lock 加锁,unlock 解锁,tryLock 尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。

NSLocking 协议

@protocol NSLocking

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

@end
- (void)log {
    // 加互斥锁
    [self.lock lock];
    NSLog(@"%@",self.array[self.index++]);
    // 解互斥锁
    [self.lock unlock];
}

条件锁

NSConditionLock

NSConditionLock 同样遵循了NSLocking 协议,除此之外,还可以设置自定义条件来获得锁和释放锁.

#import "NSConditionLockDemo.h"
@interface NSConditionLockDemo ()
@property (nonatomic, strong) NSMutableArray *images;
@property (assign) NSUInteger index;
@property (nonatomic, strong) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
static const NSInteger lockTag = 10086;
static const NSInteger imageCount = 100;
- (instancetype)init {
    self = [super init];
    if (self) {
        self.conditionLock = [[NSConditionLock alloc] init];
    }
    return self;
}
- (void)run {
    for (NSUInteger i = 0; i < imageCount; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self downloadImages];
        });
    }
    [self showImages];
}
/// 异步下载 imageCount 张图片
- (void)downloadImages {
    sleep(arc4random_uniform(5));
    [self.conditionLock lock];
    [self.images addObject:@(arc4random_uniform(100))];
    [self.conditionLock unlock];
    if (self.images.count == imageCount) {
        [self.conditionLock unlockWithCondition:lockTag];
    }
}
/// 展示下载好的 imageCount 张图片
- (void)showImages {
    [self.conditionLock lockWhenCondition:lockTag];
    [self.images enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"show: %@",obj);
    }];
    [self.conditionLock unlockWithCondition:lockTag];
}
- (NSMutableArray *)images {
    if(!_images){
        _images = [NSMutableArray array];
    }
    return _images;
}
@end

递归锁

NSRecursiveLock

@interface NSRecursiveLockDemo ()
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;
@end

@implementation NSRecursiveLockDemo
- (instancetype)init {
    self = [super init];
    if (self) {
        self.recursiveLock = [[NSRecursiveLock alloc] init];
    }
    return self;
}
- (void)run {
    NSInteger n = 3;
    NSLog(@"%ld! = %ld",(long)n, (long)[self sum:3]);
}
- (NSInteger)sum:(NSUInteger)n {
    [self.recursiveLock lock];
    NSInteger result = 0;
    if (n <= 1) {
        result = n;
    } else {
        result = [self sum:n - 1] * n;
    }
    [self.recursiveLock unlock];
    return result;
}
@end

如果将 NSRecursiveLock 换成 NSLock 就会造成死锁。

@synchronized

- (void)lock1 {
    @synchronized (self) {
        // 加锁操作
    }
}

性能最差